#include #include #include #include "store.h" #include "facade.h" #include "resourceconfig.h" #include "modelresult.h" #include "resultprovider.h" #include "facadefactory.h" #include "test.h" #include "asyncutils.h" template class TestDummyResourceFacade : public Sink::StoreFacade { public: static std::shared_ptr> registerFacade(const QByteArray &instanceIdentifier = QByteArray()) { static QMap>> map; auto facade = std::make_shared>(); map.insert(instanceIdentifier, facade); bool alwaysReturnFacade = instanceIdentifier.isEmpty(); Sink::FacadeFactory::instance().registerFacade>("dummyresource", [alwaysReturnFacade](const Sink::ResourceContext &context) { if (alwaysReturnFacade) { Q_ASSERT(map.contains(QByteArray())); return map.value(QByteArray()); } Q_ASSERT(map.contains(context.instanceId())); return map.value(context.instanceId()); }); return facade; } ~TestDummyResourceFacade(){}; KAsync::Job create(const T &domainObject) Q_DECL_OVERRIDE { SinkLogCtx(Sink::Log::Context{"test"}) << "Create: " << domainObject; creations << domainObject; return KAsync::null(); }; KAsync::Job modify(const T &domainObject) Q_DECL_OVERRIDE { SinkLogCtx(Sink::Log::Context{"test"}) << "Modify: " << domainObject; modifications << domainObject; return KAsync::null(); }; KAsync::Job move(const T &domainObject, const QByteArray &) Q_DECL_OVERRIDE { return KAsync::null(); }; KAsync::Job copy(const T &domainObject, const QByteArray &) Q_DECL_OVERRIDE { return KAsync::null(); }; KAsync::Job remove(const T &domainObject) Q_DECL_OVERRIDE { SinkLogCtx(Sink::Log::Context{"test"}) << "Remove: " << domainObject; removals << domainObject; return KAsync::null(); }; QPair, typename Sink::ResultEmitter::Ptr> load(const Sink::Query &query, const Sink::Log::Context &ctx) Q_DECL_OVERRIDE { auto resultProvider = QSharedPointer>::create(); resultProvider->onDone([resultProvider,ctx]() { SinkTraceCtx(ctx) << "Result provider is done"; }); // We have to do it this way, otherwise we're not setting the fetcher right auto emitter = resultProvider->emitter(); resultProvider->setFetcher([query, resultProvider, this, ctx](const typename T::Ptr &parent) { async::run([=] { if (parent) { SinkTraceCtx(ctx) << "Running the fetcher " << parent->identifier(); } else { SinkTraceCtx(ctx) << "Running the fetcher."; } SinkTraceCtx(ctx) << "-------------------------."; int count = 0; for (int i = offset; i < results.size(); i++) { const auto res = results.at(i); count++; // SinkTraceCtx(ctx) << "Parent filter " << query.getFilter("parent").value.toByteArray() << res->identifier() << res->getProperty("parent").toByteArray(); auto parentProperty = res->getProperty("parent").toByteArray(); if ((!parent && parentProperty.isEmpty()) || (parent && parentProperty == parent->identifier()) || query.parentProperty().isEmpty()) { // SinkTraceCtx(ctx) << "Found a hit" << res->identifier(); resultProvider->add(res); } if (query.limit()) { if (count >= query.limit()) { SinkTraceCtx(ctx) << "Aborting early after " << count << "results."; offset = i + 1; bool fetchedAll = (i + 1 >= results.size()); resultProvider->initialResultSetComplete(parent, fetchedAll); return 0; } } } resultProvider->initialResultSetComplete(parent, true); return 0; }, runAsync).exec(); }); auto job = KAsync::start([query, resultProvider]() {}); mResultProvider = resultProvider.data(); return qMakePair(job, emitter); } QList results; Sink::ResultProviderInterface *mResultProvider; bool runAsync = false; int offset = 0; QList creations; QList modifications; QList removals; }; /** * Test of the client api implementation. * * This test works with injected dummy facades and thus doesn't write to storage. */ class ClientAPITest : public QObject { Q_OBJECT template std::shared_ptr > setupFacade(const QByteArray &identifier) { auto facade = TestDummyResourceFacade::registerFacade(identifier); ResourceConfig::addResource(identifier, "dummyresource"); QMap config = ResourceConfig::getConfiguration(identifier); config.insert(Sink::ApplicationDomain::SinkResource::Capabilities::name, QVariant::fromValue(QByteArrayList() << Sink::ApplicationDomain::getTypeName())); ResourceConfig::configureResource(identifier, config); return facade; } private slots: void initTestCase() { Sink::Test::initTest(); Sink::FacadeFactory::instance().resetFactory(); ResourceConfig::clear(); } void testLoad() { auto facade = setupFacade("dummyresource.instance1"); facade->results << QSharedPointer::create("resource", "id", 0, QSharedPointer::create()); Sink::Query query; query.resourceFilter("dummyresource.instance1"); auto model = Sink::Store::loadModel(query); QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); QCOMPARE(model->rowCount(QModelIndex()), 1); } void testLoadWithoutResource() { Sink::Query query; query.resourceFilter("nonexisting.resource"); auto model = Sink::Store::loadModel(query); QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); } void testModelSingle() { auto facade = setupFacade("dummyresource.instance1"); facade->results << QSharedPointer::create("resource", "id", 0, QSharedPointer::create()); Sink::Query query; query.resourceFilter("dummyresource.instance1"); auto model = Sink::Store::loadModel(query); QTRY_COMPARE(model->rowCount(), 1); } void testModelNested() { auto facade = setupFacade("dummyresource.instance1"); auto folder = QSharedPointer::create("resource", "id", 0, QSharedPointer::create()); auto subfolder = QSharedPointer::create("resource", "subId", 0, QSharedPointer::create()); subfolder->setParent("id"); facade->results << folder << subfolder; // Test Sink::Query query; query.resourceFilter("dummyresource.instance1"); query.requestTree("parent"); auto model = Sink::Store::loadModel(query); QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); QCOMPARE(model->rowCount(), 1); model->fetchMore(model->index(0, 0)); QTRY_VERIFY(model->data(model->index(0, 0), Sink::Store::ChildrenFetchedRole).toBool()); QCOMPARE(model->rowCount(model->index(0, 0)), 1); } void testModelSignals() { auto facade = setupFacade("dummyresource.instance1"); auto folder = QSharedPointer::create("resource", "id", 0, QSharedPointer::create()); auto subfolder = QSharedPointer::create("resource", "subId", 0, QSharedPointer::create()); subfolder->setParent("id"); facade->results << folder << subfolder; // Test Sink::Query query; query.resourceFilter("dummyresource.instance1"); query.requestTree("parent"); auto model = Sink::Store::loadModel(query); QSignalSpy spy(model.data(), SIGNAL(rowsInserted(const QModelIndex &, int, int))); QVERIFY(spy.isValid()); model->fetchMore(model->index(0, 0)); QTRY_VERIFY(spy.count() >= 1); } void testModelNestedLive() { auto facade = setupFacade("dummyresource.instance1"); auto folder = QSharedPointer::create("dummyresource.instance1", "id", 0, QSharedPointer::create()); auto subfolder = QSharedPointer::create("dummyresource.instance1", "subId", 0, QSharedPointer::create()); subfolder->setParent("id"); facade->results << folder << subfolder; // Test Sink::Query query; query.resourceFilter("dummyresource.instance1"); query.setFlags(Sink::Query::LiveQuery); query.requestTree("parent"); auto model = Sink::Store::loadModel(query); QTRY_COMPARE(model->rowCount(), 1); model->fetchMore(model->index(0, 0)); QTRY_COMPARE(model->rowCount(model->index(0, 0)), 1); auto resultProvider = facade->mResultProvider; // Test new toplevel folder { QSignalSpy rowsInsertedSpy(model.data(), SIGNAL(rowsInserted(const QModelIndex &, int, int))); auto folder2 = QSharedPointer::create("resource", "id2", 0, QSharedPointer::create()); resultProvider->add(folder2); QTRY_COMPARE(model->rowCount(), 2); QTRY_COMPARE(rowsInsertedSpy.count(), 1); QCOMPARE(rowsInsertedSpy.at(0).at(0).value(), QModelIndex()); } // Test changed name { QSignalSpy dataChanged(model.data(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector &))); folder->setProperty("subject", "modifiedSubject"); resultProvider->modify(folder); QTRY_COMPARE(model->rowCount(), 2); QTRY_COMPARE(dataChanged.count(), 1); } // Test removal { QSignalSpy rowsRemovedSpy(model.data(), SIGNAL(rowsRemoved(const QModelIndex &, int, int))); folder->setProperty("subject", "modifiedSubject"); resultProvider->remove(subfolder); QTRY_COMPARE(model->rowCount(model->index(0, 0)), 0); QTRY_COMPARE(rowsRemovedSpy.count(), 1); } // TODO: A modification can also be a move } void testLoadMultiResource() { auto facade1 = setupFacade("dummyresource.instance1"); facade1->results << QSharedPointer::create("resource1", "id", 0, QSharedPointer::create()); auto facade2 = setupFacade("dummyresource.instance2"); facade2->results << QSharedPointer::create("resource2", "id", 0, QSharedPointer::create()); Sink::Query query; int childrenFetchedCount = 0; auto model = Sink::Store::loadModel(query); QObject::connect(model.data(), &QAbstractItemModel::dataChanged, [&childrenFetchedCount](const QModelIndex &, const QModelIndex &, const QVector &roles) { if (roles.contains(Sink::Store::ChildrenFetchedRole)) { childrenFetchedCount++; } }); QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); QCOMPARE(model->rowCount(QModelIndex()), 2); // Ensure children fetched is only emitted once (when all resources are done) QTest::qWait(50); QVERIFY(childrenFetchedCount <= 1); } void testImperativeLoad() { auto facade = setupFacade("dummyresource.instance1"); facade->results << QSharedPointer::create("resource", "id", 0, QSharedPointer::create()); Sink::Query query; query.resourceFilter("dummyresource.instance1"); bool gotValue = false; auto result = Sink::Store::fetchOne(query) .then([&gotValue](const Sink::ApplicationDomain::Event &event) { gotValue = true; }) .exec(); result.waitForFinished(); QVERIFY(!result.errorCode()); QVERIFY(gotValue); } void testMultiresourceIncrementalLoad() { auto facade1 = setupFacade("dummyresource.instance1"); for (int i = 0; i < 4; i++) { facade1->results << QSharedPointer::create("resource1", "id" + QByteArray::number(i), 0, QSharedPointer::create()); } auto facade2 = setupFacade("dummyresource.instance2"); for (int i = 0; i < 6; i++) { facade2->results << QSharedPointer::create("resource2", "id" + QByteArray::number(i), 0, QSharedPointer::create()); } Sink::Query query; query.limit(2); auto model = Sink::Store::loadModel(query); QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); QCOMPARE(model->rowCount(QModelIndex()), 4); //Try to fetch another round QVERIFY(model->canFetchMore(QModelIndex())); model->fetchMore(QModelIndex()); QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); QCOMPARE(model->rowCount(QModelIndex()), 8); //Try to fetch the last round QVERIFY(model->canFetchMore(QModelIndex())); model->fetchMore(QModelIndex()); QTRY_VERIFY(model->data(QModelIndex(), Sink::Store::ChildrenFetchedRole).toBool()); QCOMPARE(model->rowCount(QModelIndex()), 10); QVERIFY(!model->canFetchMore(QModelIndex())); } void testCreateModifyDelete() { auto facade = setupFacade("dummyresource.instance1"); auto event = Sink::ApplicationDomain::Event::createEntity("dummyresource.instance1"); Sink::Store::create(event).exec().waitForFinished(); QCOMPARE(facade->creations.size(), 1); Sink::Store::modify(event).exec().waitForFinished(); QCOMPARE(facade->modifications.size(), 1); Sink::Store::remove(event).exec().waitForFinished(); QCOMPARE(facade->removals.size(), 1); } void testMultiModify() { auto facade = setupFacade("dummyresource.instance1"); facade->results << QSharedPointer::create("dummyresource.instance1", "id1", 0, QSharedPointer::create()); facade->results << QSharedPointer::create("dummyresource.instance1", "id2", 0, QSharedPointer::create()); Sink::Query query; query.resourceFilter("dummyresource.instance1"); auto event = Sink::ApplicationDomain::Event::createEntity("dummyresource.instance1"); event.setUid("modifiedUid"); Sink::Store::modify(query, event).exec().waitForFinished(); QCOMPARE(facade->modifications.size(), 2); for (const auto &m : facade->modifications) { QCOMPARE(m.getUid(), QString("modifiedUid")); } } void testModelStress() { auto facade = setupFacade("dummyresource.instance1"); facade->runAsync = true; for (int i = 0; i < 100; i++) { facade->results << QSharedPointer::create("resource", "id" + QByteArray::number(i), 0, QSharedPointer::create()); } Sink::Query query; query.resourceFilter("dummyresource.instance1"); for (int i = 0; i < 100; i++) { auto model = Sink::Store::loadModel(query); model->fetchMore(QModelIndex()); QTest::qWait(1); } QTest::qWait(100); } }; QTEST_MAIN(ClientAPITest) #include "clientapitest.moc"