File indexing completed on 2024-05-12 05:26:21

0001 #include <QTest>
0002 #include <QSignalSpy>
0003 #include <QDebug>
0004 #include <functional>
0005 
0006 #include "store.h"
0007 #include "facade.h"
0008 #include "resourceconfig.h"
0009 #include "modelresult.h"
0010 #include "resultprovider.h"
0011 #include "facadefactory.h"
0012 #include "test.h"
0013 #include "asyncutils.h"
0014 
0015 template <typename T>
0016 struct Result {
0017     bool fetchedAll;
0018 };
0019 
0020 template <typename T>
0021 class TestDummyResourceFacade : public Sink::StoreFacade<T>
0022 {
0023 public:
0024     static std::shared_ptr<TestDummyResourceFacade<T>> registerFacade(const QByteArray &instanceIdentifier = QByteArray())
0025     {
0026         static QMap<QByteArray, std::shared_ptr<TestDummyResourceFacade<T>>> map;
0027         auto facade = std::make_shared<TestDummyResourceFacade<T>>();
0028         map.insert(instanceIdentifier, facade);
0029         bool alwaysReturnFacade = instanceIdentifier.isEmpty();
0030         Sink::FacadeFactory::instance().registerFacade<T, TestDummyResourceFacade<T>>("dummyresource", [alwaysReturnFacade](const Sink::ResourceContext &context) {
0031             if (alwaysReturnFacade) {
0032                 Q_ASSERT(map.contains(QByteArray()));
0033                 return map.value(QByteArray());
0034             }
0035             Q_ASSERT(map.contains(context.instanceId()));
0036             return map.value(context.instanceId());
0037         });
0038         return facade;
0039     }
0040     ~TestDummyResourceFacade() override{};
0041     KAsync::Job<void> create(const T &domainObject) Q_DECL_OVERRIDE
0042     {
0043         SinkLogCtx(Sink::Log::Context{"test"}) << "Create: " <<  domainObject;
0044         creations << domainObject;
0045         return KAsync::null<void>();
0046     };
0047     KAsync::Job<void> modify(const T &domainObject) Q_DECL_OVERRIDE
0048     {
0049         SinkLogCtx(Sink::Log::Context{"test"}) << "Modify: " <<  domainObject;
0050         modifications << domainObject;
0051         return KAsync::null<void>();
0052     };
0053     KAsync::Job<void> move(const T &domainObject, const QByteArray &) Q_DECL_OVERRIDE
0054     {
0055         return KAsync::null<void>();
0056     };
0057     KAsync::Job<void> copy(const T &domainObject, const QByteArray &) Q_DECL_OVERRIDE
0058     {
0059         return KAsync::null<void>();
0060     };
0061     KAsync::Job<void> remove(const T &domainObject) Q_DECL_OVERRIDE
0062     {
0063         SinkLogCtx(Sink::Log::Context{"test"}) << "Remove: " <<  domainObject;
0064         removals << domainObject;
0065         return KAsync::null<void>();
0066     };
0067     QPair<KAsync::Job<void>, typename Sink::ResultEmitter<typename T::Ptr>::Ptr> load(const Sink::Query &query, const Sink::Log::Context &ctx) Q_DECL_OVERRIDE
0068     {
0069         auto resultProvider = QSharedPointer<Sink::ResultProvider<typename T::Ptr>>::create();
0070         resultProvider->onDone([resultProvider,ctx]() {
0071             SinkTraceCtx(ctx) << "Result provider is done";
0072         });
0073         // We have to do it this way, otherwise we're not setting the fetcher right
0074         auto emitter = resultProvider->emitter();
0075 
0076         resultProvider->setFetcher([query, resultProvider, this, ctx]() {
0077             async::run<Result<T>>([=] {
0078                 SinkTraceCtx(ctx) << "Running the fetcher.";
0079                 SinkTraceCtx(ctx) << "-------------------------.";
0080                 int count = 0;
0081                 for (int i = offset; i < results.size(); i++) {
0082                     const auto res = results.at(i);
0083                     count++;
0084                     resultProvider->add(res);
0085                     if (query.limit()) {
0086                         if (count >= query.limit()) {
0087                             SinkTraceCtx(ctx) << "Aborting early after " << count << "results.";
0088                             offset = i + 1;
0089                             bool fetchedAll = (i + 1 >= results.size());
0090                             return Result<T>{fetchedAll};
0091                         }
0092                     }
0093                 }
0094                 return Result<T>{true};
0095             }, runAsync)
0096             .then([=] (const Result<T> &r) {
0097                 resultProvider->initialResultSetComplete(r.fetchedAll);
0098             })
0099             .exec();
0100         });
0101         auto job = KAsync::start([query, resultProvider]() {});
0102         mResultProvider = resultProvider.data();
0103         return qMakePair(job, emitter);
0104     }
0105 
0106     QList<typename T::Ptr> results;
0107     Sink::ResultProviderInterface<typename T::Ptr> *mResultProvider;
0108     bool runAsync = false;
0109     int offset = 0;
0110     QList<T> creations;
0111     QList<T> modifications;
0112     QList<T> removals;
0113 };
0114 
0115 
0116 /**
0117  * Test of the client api implementation.
0118  *
0119  * This test works with injected dummy facades and thus doesn't write to storage.
0120  */
0121 class ClientAPITest : public QObject
0122 {
0123     Q_OBJECT
0124 
0125     template<typename T>
0126     std::shared_ptr<TestDummyResourceFacade<T> >  setupFacade(const QByteArray &identifier)
0127     {
0128         auto facade = TestDummyResourceFacade<T>::registerFacade(identifier);
0129         ResourceConfig::addResource(identifier, "dummyresource");
0130         QMap<QByteArray, QVariant> config = ResourceConfig::getConfiguration(identifier);
0131         config.insert(Sink::ApplicationDomain::SinkResource::Capabilities::name, QVariant::fromValue(QByteArrayList() << Sink::ApplicationDomain::getTypeName<T>()));
0132         ResourceConfig::configureResource(identifier, config);
0133         return facade;
0134     }
0135 
0136 private slots:
0137 
0138     void initTestCase()
0139     {
0140         Sink::Test::initTest();
0141         Sink::FacadeFactory::instance().resetFactory();
0142         ResourceConfig::clear();
0143     }
0144 
0145     void testLoad()
0146     {
0147         auto facade = setupFacade<Sink::ApplicationDomain::Event>("dummyresource.instance1");
0148         facade->results << QSharedPointer<Sink::ApplicationDomain::Event>::create("resource", "id", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0149 
0150         Sink::Query query;
0151         query.resourceFilter("dummyresource.instance1");
0152 
0153         auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Event>(query);
0154         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0155         QCOMPARE(model->rowCount(QModelIndex()), 1);
0156     }
0157 
0158     void testLoadWithoutResource()
0159     {
0160         Sink::Query query;
0161         query.resourceFilter("nonexisting.resource");
0162 
0163         auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Event>(query);
0164         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0165     }
0166 
0167     void testModelSingle()
0168     {
0169         auto facade = setupFacade<Sink::ApplicationDomain::Folder>("dummyresource.instance1");
0170         facade->results << QSharedPointer<Sink::ApplicationDomain::Folder>::create("resource", "id", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0171 
0172         Sink::Query query;
0173         query.resourceFilter("dummyresource.instance1");
0174 
0175         auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query);
0176         QTRY_COMPARE(model->rowCount(), 1);
0177     }
0178 
0179     void testModelSignals()
0180     {
0181         auto facade = setupFacade<Sink::ApplicationDomain::Folder>("dummyresource.instance1");
0182         facade->runAsync = true;
0183         auto folder = QSharedPointer<Sink::ApplicationDomain::Folder>::create("resource", "id", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0184         auto subfolder = QSharedPointer<Sink::ApplicationDomain::Folder>::create("resource", "subId", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0185         subfolder->setParent("id");
0186         facade->results << folder << subfolder;
0187 
0188         // Test
0189         Sink::Query query;
0190         query.resourceFilter("dummyresource.instance1");
0191         query.requestTree("parent");
0192 
0193         auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query);
0194         QSignalSpy spy(model.data(), SIGNAL(rowsInserted(const QModelIndex &, int, int)));
0195         QVERIFY(spy.isValid());
0196         QTRY_VERIFY(spy.count() == 2);
0197     }
0198 
0199     void testModelNested()
0200     {
0201         auto facade = setupFacade<Sink::ApplicationDomain::Folder>("dummyresource.instance1");
0202         facade->runAsync = true;
0203         auto folder = QSharedPointer<Sink::ApplicationDomain::Folder>::create("resource", "id", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0204         auto subfolder = QSharedPointer<Sink::ApplicationDomain::Folder>::create("resource", "subId", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0205         subfolder->setParent("id");
0206         facade->results << folder << subfolder;
0207 
0208         // Test
0209         Sink::Query query;
0210         query.resourceFilter("dummyresource.instance1");
0211         query.requestTree("parent");
0212 
0213         auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query);
0214         QObject::connect(model.data(), &QAbstractItemModel::rowsInserted, [&] (const QModelIndex &parent, int first, int last) {
0215             for (int row = first; row <= last; row++) {
0216                 auto index = model->index(row, 0, parent);
0217                 QVERIFY(index.isValid());
0218                 QVERIFY(index.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Folder::Ptr>());
0219             }
0220         });
0221         QTRY_COMPARE(model->rowCount(), 1);
0222         QTRY_COMPARE(model->rowCount(model->index(0, 0)), 1);
0223     }
0224 
0225     void testModelNestedReverse()
0226     {
0227         auto facade = setupFacade<Sink::ApplicationDomain::Folder>("dummyresource.instance1");
0228         facade->runAsync = true;
0229         auto folder = QSharedPointer<Sink::ApplicationDomain::Folder>::create("resource", "id", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0230         auto subfolder = QSharedPointer<Sink::ApplicationDomain::Folder>::create("resource", "subId", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0231         subfolder->setParent(folder->identifier());
0232 
0233         auto subsubfolder = QSharedPointer<Sink::ApplicationDomain::Folder>::create("resource", "subsubId", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0234         subsubfolder->setParent(subfolder->identifier());
0235 
0236         facade->results << subsubfolder << subfolder << folder;
0237 
0238         // Test
0239         Sink::Query query;
0240         query.resourceFilter("dummyresource.instance1");
0241         query.requestTree("parent");
0242 
0243         auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query);
0244         QObject::connect(model.data(), &QAbstractItemModel::rowsInserted, [&] (const QModelIndex &parent, int first, int last) {
0245             for (int row = first; row <= last; row++) {
0246                 auto index = model->index(row, 0, parent);
0247                 QVERIFY(index.isValid());
0248                 QVERIFY(index.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Folder::Ptr>());
0249             }
0250         });
0251         QTRY_COMPARE(model->rowCount(), 1);
0252         QVERIFY(model->index(0, 0).data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Folder::Ptr>());
0253         QTRY_COMPARE(model->rowCount(model->index(0, 0)), 1);
0254     }
0255 
0256     void testModelNestedLive()
0257     {
0258         auto facade = setupFacade<Sink::ApplicationDomain::Folder>("dummyresource.instance1");
0259         auto folder = QSharedPointer<Sink::ApplicationDomain::Folder>::create("dummyresource.instance1", "id", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0260         auto subfolder =
0261             QSharedPointer<Sink::ApplicationDomain::Folder>::create("dummyresource.instance1", "subId", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0262         subfolder->setParent("id");
0263         facade->results << folder << subfolder;
0264 
0265         // Test
0266         Sink::Query query;
0267         query.resourceFilter("dummyresource.instance1");
0268         query.setFlags(Sink::Query::LiveQuery);
0269         query.requestTree("parent");
0270 
0271         auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query);
0272         QTRY_COMPARE(model->rowCount(), 1);
0273         model->fetchMore(model->index(0, 0));
0274         QTRY_COMPARE(model->rowCount(model->index(0, 0)), 1);
0275 
0276         auto resultProvider = facade->mResultProvider;
0277 
0278         // Test new toplevel folder
0279         {
0280             QSignalSpy rowsInsertedSpy(model.data(), SIGNAL(rowsInserted(const QModelIndex &, int, int)));
0281             auto folder2 = QSharedPointer<Sink::ApplicationDomain::Folder>::create("resource", "id2", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0282             resultProvider->add(folder2);
0283             QTRY_COMPARE(model->rowCount(), 2);
0284             QTRY_COMPARE(rowsInsertedSpy.count(), 1);
0285             QCOMPARE(rowsInsertedSpy.at(0).at(0).value<QModelIndex>(), QModelIndex());
0286         }
0287 
0288         // Test changed name
0289         {
0290             QSignalSpy dataChanged(model.data(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector<int> &)));
0291             folder->setProperty("subject", "modifiedSubject");
0292             resultProvider->modify(folder);
0293             QTRY_COMPARE(model->rowCount(), 2);
0294             QTRY_COMPARE(dataChanged.count(), 1);
0295         }
0296 
0297         // Test removal
0298         {
0299             QSignalSpy rowsRemovedSpy(model.data(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)));
0300             folder->setProperty("subject", "modifiedSubject");
0301             resultProvider->remove(subfolder);
0302             QTRY_COMPARE(model->rowCount(model->index(0, 0)), 0);
0303             QTRY_COMPARE(rowsRemovedSpy.count(), 1);
0304         }
0305 
0306         // TODO: A modification can also be a move
0307     }
0308 
0309     void testLoadMultiResource()
0310     {
0311         auto facade1 = setupFacade<Sink::ApplicationDomain::Event>("dummyresource.instance1");
0312         facade1->results << QSharedPointer<Sink::ApplicationDomain::Event>::create("resource1", "id", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0313         auto facade2 = setupFacade<Sink::ApplicationDomain::Event>("dummyresource.instance2");
0314         facade2->results << QSharedPointer<Sink::ApplicationDomain::Event>::create("resource2", "id", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0315 
0316         Sink::Query query;
0317 
0318         int childrenFetchedCount = 0;
0319         auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Event>(query);
0320         QObject::connect(model.data(), &QAbstractItemModel::dataChanged, [&childrenFetchedCount](const QModelIndex &, const QModelIndex &, const QVector<int> &roles) {
0321             if (roles.contains(Sink::Store::ChildrenFetchedRole)) {
0322                 childrenFetchedCount++;
0323             }
0324         });
0325         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0326         QCOMPARE(model->rowCount(QModelIndex()), 2);
0327         // Ensure children fetched is only emitted once (when all resources are done)
0328         QTest::qWait(50);
0329         QVERIFY(childrenFetchedCount <= 1);
0330     }
0331 
0332     void testImperativeLoad()
0333     {
0334         auto facade = setupFacade<Sink::ApplicationDomain::Event>("dummyresource.instance1");
0335         facade->results << QSharedPointer<Sink::ApplicationDomain::Event>::create("resource", "id", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0336 
0337         Sink::Query query;
0338         query.resourceFilter("dummyresource.instance1");
0339 
0340         bool gotValue = false;
0341         auto result = Sink::Store::fetchOne<Sink::ApplicationDomain::Event>(query)
0342                           .then([&gotValue](const Sink::ApplicationDomain::Event &event) { gotValue = true; })
0343                           .exec();
0344         result.waitForFinished();
0345         QVERIFY(!result.errorCode());
0346         QVERIFY(gotValue);
0347     }
0348 
0349     void testMultiresourceIncrementalLoad()
0350     {
0351         auto facade1 = setupFacade<Sink::ApplicationDomain::Event>("dummyresource.instance1");
0352         for (int i = 0; i < 4; i++) {
0353             facade1->results << QSharedPointer<Sink::ApplicationDomain::Event>::create("resource1", "id" + QByteArray::number(i), 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0354         }
0355 
0356         auto facade2 = setupFacade<Sink::ApplicationDomain::Event>("dummyresource.instance2");
0357         for (int i = 0; i < 6; i++) {
0358             facade2->results << QSharedPointer<Sink::ApplicationDomain::Event>::create("resource2", "id" + QByteArray::number(i), 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0359         }
0360 
0361         Sink::Query query;
0362         query.limit(2);
0363 
0364         auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Event>(query);
0365         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0366         QCOMPARE(model->rowCount(QModelIndex()), 4);
0367 
0368         //Try to fetch another round
0369         QVERIFY(model->canFetchMore(QModelIndex()));
0370         model->fetchMore(QModelIndex());
0371         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0372         QCOMPARE(model->rowCount(QModelIndex()), 8);
0373 
0374         //Try to fetch the last round
0375         QVERIFY(model->canFetchMore(QModelIndex()));
0376         model->fetchMore(QModelIndex());
0377         QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool());
0378         QCOMPARE(model->rowCount(QModelIndex()), 10);
0379 
0380         QVERIFY(!model->canFetchMore(QModelIndex()));
0381     }
0382 
0383     void testCreateModifyDelete()
0384     {
0385         auto facade = setupFacade<Sink::ApplicationDomain::Event>("dummyresource.instance1");
0386 
0387         auto event = Sink::ApplicationDomain::Event::createEntity<Sink::ApplicationDomain::Event>("dummyresource.instance1");
0388         Sink::Store::create(event).exec().waitForFinished();
0389         QCOMPARE(facade->creations.size(), 1);
0390         //Modify something so the mdofication won't be dropped
0391         event.setExtractedSummary("foobar");
0392         Sink::Store::modify(event).exec().waitForFinished();
0393         QCOMPARE(facade->modifications.size(), 1);
0394         Sink::Store::remove(event).exec().waitForFinished();
0395         QCOMPARE(facade->removals.size(), 1);
0396 
0397     }
0398     void testMultiModify()
0399     {
0400         auto facade = setupFacade<Sink::ApplicationDomain::Event>("dummyresource.instance1");
0401         facade->results << QSharedPointer<Sink::ApplicationDomain::Event>::create("dummyresource.instance1", "id1", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0402         facade->results << QSharedPointer<Sink::ApplicationDomain::Event>::create("dummyresource.instance1", "id2", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0403 
0404         Sink::Query query;
0405         query.resourceFilter("dummyresource.instance1");
0406 
0407         auto event = Sink::ApplicationDomain::Event::createEntity<Sink::ApplicationDomain::Event>("dummyresource.instance1");
0408         event.setExtractedUid("modifiedUid");
0409         Sink::Store::modify(query, event).exec().waitForFinished();
0410         QCOMPARE(facade->modifications.size(), 2);
0411         for (const auto &m : facade->modifications) {
0412             QCOMPARE(m.getUid(), QString("modifiedUid"));
0413         }
0414     }
0415 
0416     void testAggregateModify()
0417     {
0418         auto facade = setupFacade<Sink::ApplicationDomain::Event>("dummyresource.instance1");
0419         facade->results << QSharedPointer<Sink::ApplicationDomain::Event>::create("dummyresource.instance1", "id1", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0420         facade->results << QSharedPointer<Sink::ApplicationDomain::Event>::create("dummyresource.instance1", "id2", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0421 
0422         Sink::ApplicationDomain::Event modification("dummyresource.instance1", "id1", 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0423         modification.aggregatedIds() << "id1" << "id2";
0424         modification.setExtractedUid("modifiedUid2");
0425 
0426         Sink::Store::modify(modification).exec().waitForFinished();
0427         QCOMPARE(facade->modifications.size(), 2);
0428         for (const auto &m : facade->modifications) {
0429             QCOMPARE(m.getUid(), QByteArray{"modifiedUid2"});
0430         }
0431 
0432         Sink::Store::remove(modification).exec().waitForFinished();
0433         QCOMPARE(facade->removals.size(), 2);
0434     }
0435 
0436     void testModelStress()
0437     {
0438         auto facade = setupFacade<Sink::ApplicationDomain::Folder>("dummyresource.instance1");
0439         facade->runAsync = true;
0440         for (int i = 0; i < 100; i++) {
0441             facade->results << QSharedPointer<Sink::ApplicationDomain::Folder>::create("resource", "id" + QByteArray::number(i), 0, QSharedPointer<Sink::ApplicationDomain::MemoryBufferAdaptor>::create());
0442         }
0443 
0444         Sink::Query query;
0445         query.resourceFilter("dummyresource.instance1");
0446 
0447         for (int i = 0; i < 100; i++) {
0448             auto model = Sink::Store::loadModel<Sink::ApplicationDomain::Folder>(query);
0449             model->fetchMore(QModelIndex());
0450             QTest::qWait(1);
0451         }
0452         QTest::qWait(100);
0453     }
0454 
0455 };
0456 
0457 QTEST_MAIN(ClientAPITest)
0458 #include "clientapitest.moc"