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"