File indexing completed on 2024-11-10 04:40:22
0001 /* 0002 SPDX-FileCopyrightText: 2014 Christian Mollekopf <mollekopf@kolabsys.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include <QObject> 0008 0009 #include <storage/selectquerybuilder.h> 0010 0011 #include "aktest.h" 0012 #include "dbinitializer.h" 0013 #include "entities.h" 0014 #include "fakeakonadiserver.h" 0015 0016 #include "private/scope_p.h" 0017 0018 #include <QTest> 0019 0020 using namespace Akonadi; 0021 using namespace Akonadi::Server; 0022 0023 typedef QPair<Tag, TagAttribute::List> TagTagAttributeListPair; 0024 0025 Q_DECLARE_METATYPE(Akonadi::Server::Tag::List) 0026 Q_DECLARE_METATYPE(Akonadi::Server::Tag) 0027 Q_DECLARE_METATYPE(QList<TagTagAttributeListPair>) 0028 0029 static Protocol::ChangeNotificationList extractNotifications(const QSharedPointer<QSignalSpy> ¬ificationSpy) 0030 { 0031 Protocol::ChangeNotificationList receivedNotifications; 0032 for (int q = 0; q < notificationSpy->size(); q++) { 0033 // Only one notify call 0034 if (notificationSpy->at(q).count() != 1) { 0035 qWarning() << "Error: We're assuming only one notify call."; 0036 return Protocol::ChangeNotificationList(); 0037 } 0038 const auto n = notificationSpy->at(q).first().value<Protocol::ChangeNotificationList>(); 0039 for (int i = 0; i < n.size(); i++) { 0040 // qDebug() << n.at(i); 0041 receivedNotifications.append(n.at(i)); 0042 } 0043 } 0044 return receivedNotifications; 0045 } 0046 0047 class TagHandlerTest : public QObject 0048 { 0049 Q_OBJECT 0050 0051 FakeAkonadiServer mAkonadi; 0052 0053 public: 0054 TagHandlerTest() 0055 : QObject() 0056 { 0057 qRegisterMetaType<Akonadi::Server::Tag::List>(); 0058 0059 mAkonadi.setPopulateDb(false); 0060 mAkonadi.init(); 0061 } 0062 0063 Protocol::FetchTagsResponsePtr 0064 createResponse(const Tag &tag, const QByteArray &remoteId = QByteArray(), const Protocol::Attributes &attrs = Protocol::Attributes()) 0065 { 0066 auto resp = Protocol::FetchTagsResponsePtr::create(tag.id()); 0067 resp->setGid(tag.gid().toUtf8()); 0068 resp->setParentId(tag.parentId()); 0069 resp->setType(tag.tagType().name().toUtf8()); 0070 resp->setRemoteId(remoteId); 0071 resp->setAttributes(attrs); 0072 return resp; 0073 } 0074 0075 QScopedPointer<DbInitializer> initializer; 0076 0077 private Q_SLOTS: 0078 void testStoreTag_data() 0079 { 0080 initializer.reset(new DbInitializer); 0081 Resource res = initializer->createResource("testresource"); 0082 0083 // Make sure the type exists 0084 TagType type = type.retrieveByName(QStringLiteral("PLAIN")); 0085 if (!type.isValid()) { 0086 type.setName(QStringLiteral("PLAIN")); 0087 type.insert(); 0088 } 0089 0090 QTest::addColumn<TestScenario::List>("scenarios"); 0091 QTest::addColumn<QList<QPair<Tag, TagAttribute::List>>>("expectedTags"); 0092 QTest::addColumn<Protocol::ChangeNotificationList>("expectedNotifications"); 0093 0094 { 0095 auto cmd = Protocol::CreateTagCommandPtr::create(); 0096 cmd->setGid("tag"); 0097 cmd->setParentId(0); 0098 cmd->setType("PLAIN"); 0099 cmd->setAttributes({{"TAG", "(\\\"tag2\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")"}}); 0100 0101 auto resp = Protocol::FetchTagsResponsePtr::create(1); 0102 resp->setGid(cmd->gid()); 0103 resp->setParentId(cmd->parentId()); 0104 resp->setType(cmd->type()); 0105 resp->setAttributes(cmd->attributes()); 0106 0107 TestScenario::List scenarios; 0108 scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) 0109 << TestScenario::create(5, TestScenario::ServerCmd, resp) 0110 << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateTagResponsePtr::create()); 0111 0112 Tag tag; 0113 tag.setId(1); 0114 tag.setTagType(type); 0115 tag.setParentId(0); 0116 0117 TagAttribute attribute; 0118 attribute.setTagId(1); 0119 attribute.setType("TAG"); 0120 attribute.setValue("(\\\"tag2\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")"); 0121 0122 auto notification = Protocol::TagChangeNotificationPtr::create(); 0123 notification->setOperation(Protocol::TagChangeNotification::Add); 0124 notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); 0125 notification->setTag(Protocol::FetchTagsResponse(1)); 0126 0127 QTest::newRow("uid create relation") << scenarios << QList<TagTagAttributeListPair>{{tag, {attribute}}} 0128 << Protocol::ChangeNotificationList{notification}; 0129 } 0130 0131 { 0132 auto cmd = Protocol::CreateTagCommandPtr::create(); 0133 cmd->setGid("tag2"); 0134 cmd->setParentId(1); 0135 cmd->setType("PLAIN"); 0136 cmd->setAttributes({{"TAG", "(\\\"tag3\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")"}}); 0137 0138 auto resp = Protocol::FetchTagsResponsePtr::create(2); 0139 resp->setGid(cmd->gid()); 0140 resp->setParentId(cmd->parentId()); 0141 resp->setType(cmd->type()); 0142 resp->setAttributes(cmd->attributes()); 0143 0144 TestScenario::List scenarios; 0145 scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) 0146 << TestScenario::create(5, TestScenario::ServerCmd, resp) 0147 << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateTagResponsePtr::create()); 0148 0149 Tag tag; 0150 tag.setId(2); 0151 tag.setTagType(type); 0152 tag.setParentId(1); 0153 0154 TagAttribute attribute; 0155 attribute.setTagId(2); 0156 attribute.setType("TAG"); 0157 attribute.setValue("(\\\"tag3\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")"); 0158 0159 auto notification = Protocol::TagChangeNotificationPtr::create(); 0160 notification->setOperation(Protocol::TagChangeNotification::Add); 0161 notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); 0162 notification->setTag(Protocol::FetchTagsResponse(2)); 0163 0164 QTest::newRow("create child tag") << scenarios << QList<TagTagAttributeListPair>{{tag, {attribute}}} 0165 << Protocol::ChangeNotificationList{notification}; 0166 } 0167 } 0168 0169 void testStoreTag() 0170 { 0171 QFETCH(TestScenario::List, scenarios); 0172 QFETCH(QList<TagTagAttributeListPair>, expectedTags); 0173 QFETCH(Protocol::ChangeNotificationList, expectedNotifications); 0174 0175 mAkonadi.setScenarios(scenarios); 0176 mAkonadi.runTest(); 0177 0178 const auto receivedNotifications = extractNotifications(mAkonadi.notificationSpy()); 0179 0180 QVariantList ids; 0181 QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); 0182 for (int i = 0; i < expectedNotifications.size(); i++) { 0183 QCOMPARE(*receivedNotifications.at(i), *expectedNotifications.at(i)); 0184 ids << Protocol::cmdCast<Protocol::TagChangeNotification>(receivedNotifications.at(i)).tag().id(); 0185 } 0186 0187 SelectQueryBuilder<Tag> qb; 0188 qb.addValueCondition(Tag::idColumn(), Query::In, ids); 0189 QVERIFY(qb.exec()); 0190 const Tag::List tags = qb.result(); 0191 QCOMPARE(tags.size(), expectedTags.size()); 0192 for (int i = 0; i < tags.size(); i++) { 0193 const Tag actual = tags.at(i); 0194 const Tag expected = expectedTags.at(i).first; 0195 const TagAttribute::List expectedAttrs = expectedTags.at(i).second; 0196 0197 QCOMPARE(actual.id(), expected.id()); 0198 QCOMPARE(actual.typeId(), expected.typeId()); 0199 QCOMPARE(actual.parentId(), expected.parentId()); 0200 0201 TagAttribute::List attributes = TagAttribute::retrieveFiltered(TagAttribute::tagIdColumn(), tags.at(i).id()); 0202 QCOMPARE(attributes.size(), expectedAttrs.size()); 0203 for (int j = 0; j < attributes.size(); ++j) { 0204 const TagAttribute actualAttr = attributes.at(i); 0205 const TagAttribute expectedAttr = expectedAttrs.at(i); 0206 0207 QCOMPARE(actualAttr.tagId(), expectedAttr.tagId()); 0208 QCOMPARE(actualAttr.type(), expectedAttr.type()); 0209 QCOMPARE(actualAttr.value(), expectedAttr.value()); 0210 } 0211 } 0212 } 0213 0214 void testModifyTag_data() 0215 { 0216 initializer.reset(new DbInitializer); 0217 Resource res = initializer->createResource("testresource"); 0218 Resource res2 = initializer->createResource("testresource2"); 0219 Collection col = initializer->createCollection("Col 1"); 0220 PimItem pimItem = initializer->createItem("Item 1", col); 0221 0222 Tag tag; 0223 TagType type; 0224 type.setName(QStringLiteral("PLAIN")); 0225 type.insert(); 0226 tag.setTagType(type); 0227 tag.setGid(QStringLiteral("gid")); 0228 tag.insert(); 0229 0230 pimItem.addTag(tag); 0231 0232 TagRemoteIdResourceRelation rel; 0233 rel.setRemoteId(QStringLiteral("TAG1RES2RID")); 0234 rel.setResource(res2); 0235 rel.setTag(tag); 0236 rel.insert(); 0237 0238 QTest::addColumn<TestScenario::List>("scenarios"); 0239 QTest::addColumn<Tag::List>("expectedTags"); 0240 QTest::addColumn<Protocol::ChangeNotificationList>("expectedNotifications"); 0241 { 0242 auto cmd = Protocol::ModifyTagCommandPtr::create(tag.id()); 0243 cmd->setAttributes({{"TAG", "(\\\"tag2\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")"}}); 0244 0245 TestScenario::List scenarios; 0246 scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, cmd) 0247 << TestScenario::create(5, TestScenario::ServerCmd, createResponse(tag, QByteArray(), cmd->attributes())) 0248 << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyTagResponsePtr::create()); 0249 0250 auto notification = Protocol::TagChangeNotificationPtr::create(); 0251 notification->setOperation(Protocol::TagChangeNotification::Modify); 0252 notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); 0253 notification->setTag(Protocol::FetchTagsResponse(tag.id())); 0254 0255 QTest::newRow("uid store name") << scenarios << (Tag::List() << tag) << (Protocol::ChangeNotificationList() << notification); 0256 } 0257 0258 { 0259 auto cmd = Protocol::ModifyTagCommandPtr::create(tag.id()); 0260 cmd->setRemoteId("remote1"); 0261 0262 TestScenario::List scenarios; 0263 scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(QStringLiteral("testresource")) 0264 << TestScenario::create(5, TestScenario::ClientCmd, cmd) 0265 << TestScenario::create(5, 0266 TestScenario::ServerCmd, 0267 createResponse(tag, "remote1", {{"TAG", "(\\\"tag2\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")"}})) 0268 << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyTagResponsePtr::create()); 0269 0270 // RID-only changes don't emit notifications 0271 /* 0272 Akonadi::Protocol::ChangeNotification notification; 0273 notification.setType(Protocol::ChangeNotification::Tags); 0274 notification.setOperation(Protocol::ChangeNotification::Modify); 0275 notification.setSessionId(FakeAkonadiServer::instanceName().toLatin1()); 0276 notification.addEntity(tag.id()); 0277 */ 0278 0279 QTest::newRow("uid store rid") << scenarios << (Tag::List() << tag) << Protocol::ChangeNotificationList(); 0280 } 0281 0282 { 0283 auto cmd = Protocol::ModifyTagCommandPtr::create(tag.id()); 0284 cmd->setRemoteId(QByteArray()); 0285 0286 TestScenario::List scenarios; 0287 scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(res.name()) 0288 << TestScenario::create(5, TestScenario::ClientCmd, cmd) 0289 << TestScenario::create( 0290 5, 0291 TestScenario::ServerCmd, 0292 createResponse(tag, QByteArray(), {{"TAG", "(\\\"tag2\\\" \\\"\\\" \\\"\\\" \\\"\\\" \\\"0\\\" () () \\\"-1\\\")"}})) 0293 << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyTagResponsePtr::create()); 0294 0295 // RID-only changes don't emit notifications 0296 /* 0297 Akonadi::Protocol::ChangeNotification tagChangeNtf; 0298 tagChangeNtf.setType(Protocol::ChangeNotification::Tags); 0299 tagChangeNtf.setOperation(Protocol::ChangeNotification::Modify); 0300 tagChangeNtf.setSessionId(FakeAkonadiServer::instanceName().toLatin1()); 0301 tagChangeNtf.addEntity(tag.id()); 0302 */ 0303 0304 QTest::newRow("uid store unset one rid") << scenarios << (Tag::List() << tag) << Protocol::ChangeNotificationList(); 0305 } 0306 0307 { 0308 auto cmd = Protocol::ModifyTagCommandPtr::create(tag.id()); 0309 cmd->setRemoteId(QByteArray()); 0310 0311 TestScenario::List scenarios; 0312 scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(res2.name()) 0313 << TestScenario::create(5, TestScenario::ClientCmd, cmd) 0314 << TestScenario::create(5, TestScenario::ServerCmd, Protocol::DeleteTagResponsePtr::create()) 0315 << TestScenario::create(5, TestScenario::ServerCmd, Protocol::ModifyTagResponsePtr::create()); 0316 0317 auto itemUntaggedNtf = Protocol::ItemChangeNotificationPtr::create(); 0318 itemUntaggedNtf->setOperation(Protocol::ItemChangeNotification::ModifyTags); 0319 itemUntaggedNtf->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); 0320 itemUntaggedNtf->setItems({*initializer->fetchResponse(pimItem)}); 0321 itemUntaggedNtf->setResource(res2.name().toLatin1()); 0322 itemUntaggedNtf->setParentCollection(col.id()); 0323 itemUntaggedNtf->setRemovedTags(QSet<qint64>() << tag.id()); 0324 0325 auto tagRemoveNtf = Protocol::TagChangeNotificationPtr::create(); 0326 tagRemoveNtf->setOperation(Protocol::TagChangeNotification::Remove); 0327 tagRemoveNtf->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); 0328 Protocol::FetchTagsResponse ntfTag; 0329 ntfTag.setId(tag.id()); 0330 ntfTag.setGid("gid"); 0331 ntfTag.setType("PLAIN"); 0332 tagRemoveNtf->setTag(std::move(ntfTag)); 0333 0334 QTest::newRow("uid store unset last rid") << scenarios << Tag::List() << (Protocol::ChangeNotificationList() << itemUntaggedNtf << tagRemoveNtf); 0335 } 0336 } 0337 0338 void testModifyTag() 0339 { 0340 QFETCH(TestScenario::List, scenarios); 0341 QFETCH(Tag::List, expectedTags); 0342 QFETCH(Protocol::ChangeNotificationList, expectedNotifications); 0343 0344 mAkonadi.setScenarios(scenarios); 0345 mAkonadi.runTest(); 0346 0347 const auto receivedNotifications = extractNotifications(mAkonadi.notificationSpy()); 0348 0349 QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); 0350 for (int i = 0; i < receivedNotifications.size(); i++) { 0351 qDebug() << Protocol::debugString(receivedNotifications.at(i)); 0352 qDebug() << Protocol::debugString(expectedNotifications.at(i)); 0353 QCOMPARE(*receivedNotifications.at(i), *expectedNotifications.at(i)); 0354 } 0355 0356 const Tag::List tags = Tag::retrieveAll(); 0357 QCOMPARE(tags.size(), expectedTags.size()); 0358 for (int i = 0; i < tags.size(); i++) { 0359 QCOMPARE(tags.at(i).id(), expectedTags.at(i).id()); 0360 QCOMPARE(tags.at(i).tagType().name(), expectedTags.at(i).tagType().name()); 0361 } 0362 } 0363 0364 void testRemoveTag_data() 0365 { 0366 initializer.reset(new DbInitializer); 0367 Resource res1 = initializer->createResource("testresource3"); 0368 Resource res2 = initializer->createResource("testresource4"); 0369 0370 Tag tag; 0371 TagType type; 0372 type.setName(QStringLiteral("PLAIN")); 0373 type.insert(); 0374 tag.setTagType(type); 0375 tag.setGid(QStringLiteral("gid2")); 0376 tag.insert(); 0377 0378 TagRemoteIdResourceRelation rel1; 0379 rel1.setRemoteId(QStringLiteral("TAG2RES1RID")); 0380 rel1.setResource(res1); 0381 rel1.setTag(tag); 0382 rel1.insert(); 0383 0384 TagRemoteIdResourceRelation rel2; 0385 rel2.setRemoteId(QStringLiteral("TAG2RES2RID")); 0386 rel2.setResource(res2); 0387 rel2.setTag(tag); 0388 rel2.insert(); 0389 0390 QTest::addColumn<TestScenario::List>("scenarios"); 0391 QTest::addColumn<Tag::List>("expectedTags"); 0392 QTest::addColumn<Protocol::ChangeNotificationList>("expectedNotifications"); 0393 { 0394 TestScenario::List scenarios; 0395 scenarios << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, Protocol::DeleteTagCommandPtr::create(tag.id())) 0396 << TestScenario::create(5, TestScenario::ServerCmd, Protocol::DeleteTagResponsePtr::create()); 0397 0398 auto ntf = Protocol::TagChangeNotificationPtr::create(); 0399 ntf->setOperation(Protocol::TagChangeNotification::Remove); 0400 ntf->setSessionId(FakeAkonadiServer::instanceName().toLatin1()); 0401 0402 auto res1Ntf = Protocol::TagChangeNotificationPtr::create(*ntf); 0403 Protocol::FetchTagsResponse res1NtfTag; 0404 res1NtfTag.setId(tag.id()); 0405 res1NtfTag.setRemoteId(rel1.remoteId().toLatin1()); 0406 res1Ntf->setTag(std::move(res1NtfTag)); 0407 res1Ntf->setResource(res1.name().toLatin1()); 0408 0409 auto res2Ntf = Protocol::TagChangeNotificationPtr::create(*ntf); 0410 Protocol::FetchTagsResponse res2NtfTag; 0411 res2NtfTag.setId(tag.id()); 0412 res2NtfTag.setRemoteId(rel2.remoteId().toLatin1()); 0413 res2Ntf->setTag(std::move(res2NtfTag)); 0414 res2Ntf->setResource(res2.name().toLatin1()); 0415 0416 auto clientNtf = Protocol::TagChangeNotificationPtr::create(*ntf); 0417 clientNtf->setTag(Protocol::FetchTagsResponse(tag.id())); 0418 0419 QTest::newRow("uid remove") << scenarios << Tag::List() << (Protocol::ChangeNotificationList() << res1Ntf << res2Ntf << clientNtf); 0420 } 0421 } 0422 0423 void testRemoveTag() 0424 { 0425 QFETCH(TestScenario::List, scenarios); 0426 QFETCH(Tag::List, expectedTags); 0427 QFETCH(Protocol::ChangeNotificationList, expectedNotifications); 0428 0429 mAkonadi.setScenarios(scenarios); 0430 mAkonadi.runTest(); 0431 0432 const auto receivedNotifications = extractNotifications(mAkonadi.notificationSpy()); 0433 0434 QCOMPARE(receivedNotifications.size(), expectedNotifications.count()); 0435 for (int i = 0; i < receivedNotifications.size(); i++) { 0436 QCOMPARE(*receivedNotifications.at(i), *expectedNotifications.at(i)); 0437 } 0438 0439 const Tag::List tags = Tag::retrieveAll(); 0440 QCOMPARE(tags.size(), 0); 0441 } 0442 }; 0443 0444 AKTEST_FAKESERVER_MAIN(TagHandlerTest) 0445 0446 #include "taghandlertest.moc"