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> &notificationSpy)
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"