File indexing completed on 2024-11-10 04:40:20

0001 /*
0002     SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <QObject>
0008 #include <QSettings>
0009 
0010 #include <qhashfunctions.h>
0011 #include <storage/selectquerybuilder.h>
0012 
0013 #include "private/scope_p.h"
0014 #include "private/standarddirs_p.h"
0015 
0016 #include "fakeakonadiserver.h"
0017 #include "fakeentities.h"
0018 
0019 #include "shared/akranges.h"
0020 #include "shared/aktest.h"
0021 
0022 #include <QTest>
0023 
0024 using namespace Akonadi;
0025 using namespace Akonadi::Server;
0026 
0027 Q_DECLARE_METATYPE(PimItem)
0028 Q_DECLARE_METATYPE(QList<Flag>)
0029 Q_DECLARE_METATYPE(QList<FakePart>)
0030 Q_DECLARE_METATYPE(QList<FakeTag>)
0031 
0032 class ItemCreateHandlerTest : public QObject
0033 {
0034     Q_OBJECT
0035 
0036     FakeAkonadiServer mAkonadi;
0037 
0038 public:
0039     ItemCreateHandlerTest()
0040     {
0041         QHashSeed::setDeterministicGlobalSeed();
0042 
0043         // Effectively disable external payload parts, we have a dedicated unit-test
0044         // for that
0045         const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite);
0046         QSettings settings(serverConfigFile, QSettings::IniFormat);
0047         settings.setValue(QStringLiteral("General/SizeThreshold"), std::numeric_limits<qint64>::max());
0048 
0049         mAkonadi.init();
0050     }
0051 
0052     void updatePimItem(PimItem &pimItem, const QString &remoteId, const qint64 size)
0053     {
0054         pimItem.setRemoteId(remoteId);
0055         pimItem.setGid(remoteId);
0056         pimItem.setSize(size);
0057     }
0058 
0059     void updateNotifcationEntity(Protocol::ItemChangeNotificationPtr &ntf, const PimItem &pimItem)
0060     {
0061         Protocol::FetchItemsResponse item;
0062         item.setId(pimItem.id());
0063         item.setRemoteId(pimItem.remoteId());
0064         item.setRemoteRevision(pimItem.remoteRevision());
0065         item.setMimeType(pimItem.mimeType().name());
0066         ntf->setItems({std::move(item)});
0067     }
0068 
0069     struct PartHelper {
0070         PartHelper(const QString &type_, const QByteArray &data_, qsizetype size_, Part::Storage storage_ = Part::Internal, int version_ = 0)
0071             : type(type_)
0072             , data(data_)
0073             , size(size_)
0074             , storage(storage_)
0075             , version(version_)
0076         {
0077         }
0078         QString type;
0079         QByteArray data;
0080         qsizetype size;
0081         Part::Storage storage;
0082         int version;
0083     };
0084 
0085     void updateParts(QList<FakePart> &parts, const std::vector<PartHelper> &updatedParts)
0086     {
0087         parts.clear();
0088         for (const PartHelper &helper : updatedParts) {
0089             FakePart part;
0090 
0091             const QStringList types = helper.type.split(QLatin1Char(':'));
0092             Q_ASSERT(types.count() == 2);
0093             part.setPartType(PartType(types[1], types[0]));
0094             part.setData(helper.data);
0095             part.setDatasize(helper.size);
0096             part.setStorage(helper.storage);
0097             part.setVersion(helper.version);
0098             parts << part;
0099         }
0100     }
0101 
0102     void updateFlags(QList<Flag> &flags, const QStringList &updatedFlags)
0103     {
0104         flags.clear();
0105         for (const QString &flagName : updatedFlags) {
0106             Flag flag;
0107             flag.setName(flagName);
0108             flags << flag;
0109         }
0110     }
0111 
0112     struct TagHelper {
0113         TagHelper(const QString &tagType_, const QString &gid_, const QString &remoteId_ = QString())
0114             : tagType(tagType_)
0115             , gid(gid_)
0116             , remoteId(remoteId_)
0117         {
0118         }
0119         QString tagType;
0120         QString gid;
0121         QString remoteId;
0122     };
0123     void updateTags(QList<FakeTag> &tags, const std::vector<TagHelper> &updatedTags)
0124     {
0125         tags.clear();
0126         for (const TagHelper &helper : updatedTags) {
0127             FakeTag tag;
0128 
0129             TagType tagType;
0130             tagType.setName(helper.tagType);
0131 
0132             tag.setTagType(tagType);
0133             tag.setGid(helper.gid);
0134             tag.setRemoteId(helper.remoteId);
0135             tags << tag;
0136         }
0137     }
0138 
0139     Protocol::CreateItemCommandPtr createCommand(const PimItem &pimItem, const QDateTime &dt, const QSet<QByteArray> &parts, qint64 overrideSize = -1)
0140     {
0141         const qint64 size = overrideSize > -1 ? overrideSize : pimItem.size();
0142 
0143         auto cmd = Protocol::CreateItemCommandPtr::create();
0144         cmd->setCollection(Scope(pimItem.collectionId()));
0145         cmd->setItemSize(size);
0146         cmd->setRemoteId(pimItem.remoteId());
0147         cmd->setRemoteRevision(pimItem.remoteRevision());
0148         cmd->setMimeType(pimItem.mimeType().name());
0149         cmd->setGid(pimItem.gid());
0150         cmd->setDateTime(dt);
0151         cmd->setParts(parts);
0152 
0153         return cmd;
0154     }
0155 
0156     Protocol::FetchItemsResponsePtr createResponse(qint64 expectedId,
0157                                                    const PimItem &pimItem,
0158                                                    const QDateTime &datetime,
0159                                                    const QList<Protocol::StreamPayloadResponse> &parts,
0160                                                    qint64 overrideSize = -1)
0161     {
0162         const qint64 size = overrideSize > -1 ? overrideSize : pimItem.size();
0163 
0164         auto resp = Protocol::FetchItemsResponsePtr::create(expectedId);
0165         resp->setParentId(pimItem.collectionId());
0166         resp->setSize(size);
0167         resp->setRemoteId(pimItem.remoteId());
0168         resp->setRemoteRevision(pimItem.remoteRevision());
0169         resp->setMimeType(pimItem.mimeType().name());
0170         resp->setGid(pimItem.gid());
0171         resp->setMTime(datetime);
0172         resp->setParts(parts);
0173         resp->setAncestors({Protocol::Ancestor(4, QLatin1StringView("ColC"))});
0174 
0175         return resp;
0176     }
0177 
0178     TestScenario errorResponse(const QString &errorMsg)
0179     {
0180         auto response = Protocol::CreateItemResponsePtr::create();
0181         response->setError(1, errorMsg);
0182         return TestScenario::create(5, TestScenario::ServerCmd, response);
0183     }
0184 
0185 private Q_SLOTS:
0186     void testItemCreate_data()
0187     {
0188         using Notifications = QList<Protocol::ItemChangeNotificationPtr>;
0189 
0190         QTest::addColumn<TestScenario::List>("scenarios");
0191         QTest::addColumn<Notifications>("notifications");
0192         QTest::addColumn<PimItem>("pimItem");
0193         QTest::addColumn<QList<FakePart>>("parts");
0194         QTest::addColumn<QList<Flag>>("flags");
0195         QTest::addColumn<QList<FakeTag>>("tags");
0196         QTest::addColumn<qint64>("uidnext");
0197         QTest::addColumn<QDateTime>("datetime");
0198         QTest::addColumn<bool>("expectFail");
0199 
0200         TestScenario::List scenarios;
0201         auto notification = Protocol::ItemChangeNotificationPtr::create();
0202         qint64 uidnext = 0;
0203         QDateTime datetime(QDate(2014, 05, 12), QTime(14, 46, 00), QTimeZone::UTC);
0204         PimItem pimItem;
0205         QList<FakePart> parts;
0206         QList<Flag> flags;
0207         QList<FakeTag> tags;
0208 
0209         pimItem.setCollectionId(4);
0210         pimItem.setSize(10);
0211         pimItem.setRemoteId(QStringLiteral("TEST-1"));
0212         pimItem.setRemoteRevision(QStringLiteral("1"));
0213         pimItem.setGid(QStringLiteral("TEST-1"));
0214         pimItem.setMimeType(MimeType::retrieveByName(QStringLiteral("application/octet-stream")));
0215         pimItem.setDatetime(datetime);
0216         updateParts(parts, {{QLatin1StringView("PLD:DATA"), "0123456789", 10}});
0217         notification->setOperation(Protocol::ItemChangeNotification::Add);
0218         notification->setParentCollection(4);
0219         notification->setResource("akonadi_fake_resource_0");
0220         Protocol::FetchItemsResponse item;
0221         item.setId(-1);
0222         item.setRemoteId(QStringLiteral("TEST-1"));
0223         item.setRemoteRevision(QStringLiteral("1"));
0224         item.setMimeType(QStringLiteral("application/octet-stream"));
0225         notification->setItems({std::move(item)});
0226         notification->setSessionId(FakeAkonadiServer::instanceName().toLatin1());
0227         uidnext = 13;
0228         scenarios
0229             << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:DATA"}))
0230             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData))
0231             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 10)))
0232             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data))
0233             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "0123456789"))
0234             << TestScenario::create(5,
0235                                     TestScenario::ServerCmd,
0236                                     createResponse(uidnext,
0237                                                    pimItem,
0238                                                    datetime,
0239                                                    {Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 10), "0123456789")}))
0240             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0241         QTest::newRow("single-part") << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0242 
0243         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0244         updatePimItem(pimItem, QStringLiteral("TEST-2"), 20);
0245         updateParts(parts, {{QLatin1StringView("PLD:DATA"), "Random Data", 11}, {QLatin1StringView("PLD:PLDTEST"), "Test Data", 9}});
0246         updateNotifcationEntity(notification, pimItem);
0247         ++uidnext;
0248         scenarios.clear();
0249         scenarios
0250             << FakeAkonadiServer::loginScenario()
0251             << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:DATA", "PLD:PLDTEST"}))
0252             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData))
0253             << TestScenario::create(5,
0254                                     TestScenario::ClientCmd,
0255                                     Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 11, 0)))
0256             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data))
0257             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "Random Data"))
0258             << TestScenario::create(5,
0259                                     TestScenario::ServerCmd,
0260                                     Protocol::StreamPayloadCommandPtr::create("PLD:PLDTEST", Protocol::StreamPayloadCommand::MetaData))
0261             << TestScenario::create(5,
0262                                     TestScenario::ClientCmd,
0263                                     Protocol::StreamPayloadResponsePtr::create("PLD:PLDTEST", Protocol::PartMetaData("PLD:PLDTEST", 9, 0)))
0264             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:PLDTEST", Protocol::StreamPayloadCommand::Data))
0265             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:PLDTEST", "Test Data"))
0266             << TestScenario::create(5,
0267                                     TestScenario::ServerCmd,
0268                                     createResponse(uidnext,
0269                                                    pimItem,
0270                                                    datetime,
0271                                                    {Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 11), "Random Data"),
0272                                                     Protocol::StreamPayloadResponse("PLD:PLDTEST", Protocol::PartMetaData("PLD:PLDTEST", 9), "Test Data")}))
0273             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0274         QTest::newRow("multi-part") << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0275 
0276         TestScenario inScenario;
0277         TestScenario outScenario;
0278         {
0279             auto cmd = Protocol::CreateItemCommandPtr::create();
0280             cmd->setCollection(Scope(100));
0281             inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd);
0282         }
0283         scenarios.clear();
0284         scenarios << FakeAkonadiServer::loginScenario() << inScenario << errorResponse(QStringLiteral("Invalid parent collection"));
0285         QTest::newRow("invalid collection") << scenarios << Notifications{} << PimItem() << QList<FakePart>() << QList<Flag>() << QList<FakeTag>() << -1ll
0286                                             << QDateTime() << true;
0287 
0288         {
0289             auto cmd = Protocol::CreateItemCommandPtr::create();
0290             cmd->setCollection(Scope(6));
0291             inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd);
0292         }
0293         scenarios.clear();
0294         scenarios << FakeAkonadiServer::loginScenario() << inScenario << errorResponse(QStringLiteral("Cannot append item into virtual collection"));
0295         QTest::newRow("virtual collection") << scenarios << Notifications{} << PimItem() << QList<FakePart>() << QList<Flag>() << QList<FakeTag>() << -1ll
0296                                             << QDateTime() << true;
0297 
0298         updatePimItem(pimItem, QStringLiteral("TEST-3"), 5);
0299         updateParts(parts, {{QLatin1StringView("PLD:DATA"), "12345", 5}});
0300         updateNotifcationEntity(notification, pimItem);
0301         ++uidnext;
0302         scenarios.clear();
0303         scenarios
0304             << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:DATA"}, 1))
0305             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData))
0306             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5)))
0307             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data))
0308             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "12345"))
0309             << TestScenario::create(
0310                    5,
0311                    TestScenario::ServerCmd,
0312                    createResponse(uidnext, pimItem, datetime, {Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5), "12345")}))
0313             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0314         QTest::newRow("mismatch item sizes (smaller)")
0315             << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0316 
0317         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0318         updatePimItem(pimItem, QStringLiteral("TEST-4"), 10);
0319         updateNotifcationEntity(notification, pimItem);
0320         ++uidnext;
0321         scenarios.clear();
0322         scenarios
0323             << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:DATA"}, 10))
0324             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData))
0325             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5)))
0326             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data))
0327             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "12345"))
0328             << TestScenario::create(5,
0329                                     TestScenario::ServerCmd,
0330                                     createResponse(uidnext,
0331                                                    pimItem,
0332                                                    datetime,
0333                                                    {Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5), "12345")},
0334                                                    10))
0335             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0336         QTest::newRow("mismatch item sizes (bigger)") << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime
0337                                                       << false;
0338 
0339         scenarios.clear();
0340         scenarios
0341             << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:DATA"}))
0342             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData))
0343             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 5)))
0344             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data))
0345             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "123"))
0346             << errorResponse(QStringLiteral("Payload size mismatch"));
0347         QTest::newRow("incomplete part data") << scenarios << Notifications{} << PimItem() << QList<FakePart>() << QList<Flag>() << QList<FakeTag>() << -1ll
0348                                               << QDateTime() << true;
0349 
0350         scenarios.clear();
0351         scenarios
0352             << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:DATA"}))
0353             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData))
0354             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 4)))
0355             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data))
0356             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", "1234567890"))
0357             << errorResponse(QStringLiteral("Payload size mismatch"));
0358         QTest::newRow("part data larger than advertised")
0359             << scenarios << Notifications{} << PimItem() << QList<FakePart>() << QList<Flag>() << QList<FakeTag>() << -1ll << QDateTime() << true;
0360 
0361         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0362         updatePimItem(pimItem, QStringLiteral("TEST-5"), 0);
0363         updateParts(parts, {{QLatin1StringView("PLD:DATA"), QByteArray(), 0}});
0364         updateNotifcationEntity(notification, pimItem);
0365         ++uidnext;
0366         scenarios.clear();
0367         scenarios
0368             << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:DATA"}))
0369             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData))
0370             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 0)))
0371             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data))
0372             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", QByteArray()))
0373             << TestScenario::create(5,
0374                                     TestScenario::ServerCmd,
0375                                     createResponse(uidnext,
0376                                                    pimItem,
0377                                                    datetime,
0378                                                    {Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 0), QByteArray())}))
0379             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0380         QTest::newRow("empty payload part") << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0381 
0382         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0383         updatePimItem(pimItem, QStringLiteral("TEST-8"), 1);
0384         updateParts(parts, {{QLatin1StringView("PLD:DATA"), QByteArray("\0", 1), 1}});
0385         updateNotifcationEntity(notification, pimItem);
0386         ++uidnext;
0387         scenarios.clear();
0388         scenarios
0389             << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:DATA"}))
0390             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData))
0391             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 1)))
0392             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data))
0393             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", QByteArray("\0", 1)))
0394             << TestScenario::create(5,
0395                                     TestScenario::ServerCmd,
0396                                     createResponse(uidnext,
0397                                                    pimItem,
0398                                                    datetime,
0399                                                    {Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", 1), QByteArray("\0", 1))}))
0400             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0401         QTest::newRow("part data will null character")
0402             << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0403 
0404         const QString utf8String = QStringLiteral("äöüß@€µøđ¢©®");
0405         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0406         updatePimItem(pimItem, QStringLiteral("TEST-9"), utf8String.toUtf8().size());
0407         updateParts(parts, {{QLatin1StringView("PLD:DATA"), utf8String.toUtf8(), utf8String.toUtf8().size()}});
0408         updateNotifcationEntity(notification, pimItem);
0409         ++uidnext;
0410         scenarios.clear();
0411         scenarios
0412             << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:DATA"}))
0413             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData))
0414             << TestScenario::create(5,
0415                                     TestScenario::ClientCmd,
0416                                     Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize())))
0417             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data))
0418             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", utf8String.toUtf8()))
0419             << TestScenario::create(
0420                    5,
0421                    TestScenario::ServerCmd,
0422                    createResponse(
0423                        uidnext,
0424                        pimItem,
0425                        datetime,
0426                        {Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", utf8String.toUtf8().size()), utf8String.toUtf8())}))
0427             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0428         QTest::newRow("utf8 part data") << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0429 
0430         const QByteArray hugeData = QByteArray("a").repeated(1 << 20);
0431         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0432         updatePimItem(pimItem, QStringLiteral("TEST-10"), 1 << 20);
0433         updateParts(parts, {{QLatin1StringView("PLD:DATA"), hugeData, 1 << 20}});
0434         updateNotifcationEntity(notification, pimItem);
0435         ++uidnext;
0436         scenarios.clear();
0437         scenarios
0438             << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:DATA"}))
0439             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData))
0440             << TestScenario::create(5,
0441                                     TestScenario::ClientCmd,
0442                                     Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize())))
0443             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data))
0444             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", hugeData))
0445             << TestScenario::create(
0446                    5,
0447                    TestScenario::ServerCmd,
0448                    createResponse(uidnext,
0449                                   pimItem,
0450                                   datetime,
0451                                   {Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()), hugeData)}))
0452             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0453         QTest::newRow("huge part data") << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0454 
0455         const QByteArray dataWithNewLines = "Bernard, Bernard, Bernard, Bernard, look, look Bernard!\nWHAT!!!!!!!\nI'm a prostitute robot from the future!";
0456         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0457         updatePimItem(pimItem, QStringLiteral("TEST-11"), dataWithNewLines.size());
0458         updateParts(parts, {{QLatin1StringView("PLD:DATA"), dataWithNewLines, dataWithNewLines.size()}});
0459         updateNotifcationEntity(notification, pimItem);
0460         ++uidnext;
0461         scenarios.clear();
0462         scenarios
0463             << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:DATA"}))
0464             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData))
0465             << TestScenario::create(5,
0466                                     TestScenario::ClientCmd,
0467                                     Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize())))
0468             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data))
0469             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", dataWithNewLines))
0470             << TestScenario::create(
0471                    5,
0472                    TestScenario::ServerCmd,
0473                    createResponse(uidnext,
0474                                   pimItem,
0475                                   datetime,
0476                                   {Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", dataWithNewLines.size()), dataWithNewLines)}))
0477             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0478         QTest::newRow("data with newlines") << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0479 
0480         const QByteArray lotsOfNewlines = QByteArray("\n").repeated(1 << 20);
0481         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0482         updatePimItem(pimItem, QStringLiteral("TEST-12"), lotsOfNewlines.size());
0483         updateParts(parts, {{QLatin1StringView("PLD:DATA"), lotsOfNewlines, lotsOfNewlines.size()}});
0484         updateNotifcationEntity(notification, pimItem);
0485         ++uidnext;
0486         scenarios.clear();
0487         scenarios
0488             << FakeAkonadiServer::loginScenario() << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:DATA"}))
0489             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::MetaData))
0490             << TestScenario::create(5,
0491                                     TestScenario::ClientCmd,
0492                                     Protocol::StreamPayloadResponsePtr::create("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize())))
0493             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::StreamPayloadCommandPtr::create("PLD:DATA", Protocol::StreamPayloadCommand::Data))
0494             << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:DATA", lotsOfNewlines))
0495             << TestScenario::create(
0496                    5,
0497                    TestScenario::ServerCmd,
0498                    createResponse(uidnext,
0499                                   pimItem,
0500                                   datetime,
0501                                   {Protocol::StreamPayloadResponse("PLD:DATA", Protocol::PartMetaData("PLD:DATA", parts.first().datasize()), lotsOfNewlines)}))
0502             << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0503         QTest::newRow("data with lots of newlines") << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime
0504                                                     << false;
0505 
0506         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0507         updatePimItem(pimItem, QStringLiteral("TEST-13"), 20);
0508         updateParts(parts, {{QLatin1StringView("PLD:NEWPARTTYPE1"), "0123456789", 10}, {QLatin1StringView("PLD:NEWPARTTYPE2"), "9876543210", 10}});
0509         updateNotifcationEntity(notification, pimItem);
0510         ++uidnext;
0511         scenarios.clear();
0512         scenarios << FakeAkonadiServer::loginScenario()
0513                   << TestScenario::create(5, TestScenario::ClientCmd, createCommand(pimItem, datetime, {"PLD:NEWPARTTYPE1", "PLD:NEWPARTTYPE2"}))
0514                   << TestScenario::create(5,
0515                                           TestScenario::ServerCmd,
0516                                           Protocol::StreamPayloadCommandPtr::create("PLD:NEWPARTTYPE1", Protocol::StreamPayloadCommand::MetaData))
0517                   << TestScenario::create(5,
0518                                           TestScenario::ClientCmd,
0519                                           Protocol::StreamPayloadResponsePtr::create("PLD:NEWPARTTYPE1", Protocol::PartMetaData("PLD:NEWPARTTYPE1", 10)))
0520                   << TestScenario::create(5,
0521                                           TestScenario::ServerCmd,
0522                                           Protocol::StreamPayloadCommandPtr::create("PLD:NEWPARTTYPE1", Protocol::StreamPayloadCommand::Data))
0523                   << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:NEWPARTTYPE2", "0123456789"))
0524                   << TestScenario::create(5,
0525                                           TestScenario::ServerCmd,
0526                                           Protocol::StreamPayloadCommandPtr::create("PLD:NEWPARTTYPE2", Protocol::StreamPayloadCommand::MetaData))
0527                   << TestScenario::create(5,
0528                                           TestScenario::ClientCmd,
0529                                           Protocol::StreamPayloadResponsePtr::create("PLD:NEWPARTTYPE2", Protocol::PartMetaData("PLD:NEWPARTTYPE2", 10)))
0530                   << TestScenario::create(5,
0531                                           TestScenario::ServerCmd,
0532                                           Protocol::StreamPayloadCommandPtr::create("PLD:NEWPARTTYPE2", Protocol::StreamPayloadCommand::Data))
0533                   << TestScenario::create(5, TestScenario::ClientCmd, Protocol::StreamPayloadResponsePtr::create("PLD:NEWPARTTYPE2", "9876543210"))
0534                   << TestScenario::create(
0535                          5,
0536                          TestScenario::ServerCmd,
0537                          createResponse(uidnext,
0538                                         pimItem,
0539                                         datetime,
0540                                         {Protocol::StreamPayloadResponse("PLD:NEWPARTTYPE1", Protocol::PartMetaData("PLD:NEWPARTTYPE1", 10), "0123456789"),
0541                                          Protocol::StreamPayloadResponse("PLD:NEWPARTTYPE2", Protocol::PartMetaData("PLD:NEWPARTTYPE2", 10), "9876543210")}))
0542                   << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0543         QTest::newRow("non-existent part types") << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime
0544                                                  << false;
0545 
0546         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0547         updatePimItem(pimItem, QStringLiteral("TEST-14"), 0);
0548         updateParts(parts, {});
0549         updateFlags(flags, QStringList() << QStringLiteral("\\SEEN") << QStringLiteral("\\RANDOM"));
0550         updateNotifcationEntity(notification, pimItem);
0551         ++uidnext;
0552         {
0553             auto cmd = createCommand(pimItem, datetime, {});
0554             cmd->setFlags({"\\SEEN", "\\RANDOM"});
0555             inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd);
0556 
0557             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0558             rsp->setFlags({"\\SEEN", "\\RANDOM"});
0559             outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp);
0560         }
0561         scenarios.clear();
0562         scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario
0563                   << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0564         QTest::newRow("item with flags") << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0565 
0566         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0567         updatePimItem(pimItem, QStringLiteral("TEST-15"), 0);
0568         updateFlags(flags, {});
0569         updateTags(tags, {{QLatin1StringView("PLAIN"), QLatin1StringView("TAG-1")}, {QLatin1StringView("PLAIN"), QLatin1StringView("TAG-2")}});
0570         updateNotifcationEntity(notification, pimItem);
0571         ++uidnext;
0572         {
0573             auto cmd = createCommand(pimItem, datetime, {});
0574             cmd->setTags(Scope(Scope::Gid, {QLatin1StringView("TAG-1"), QLatin1StringView("TAG-2")}));
0575             inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd);
0576 
0577             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0578             rsp->setTags({Protocol::FetchTagsResponse(2, "TAG-1", "PLAIN"), Protocol::FetchTagsResponse(3, "TAG-2", "PLAIN")});
0579             outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp);
0580         }
0581         scenarios.clear();
0582         scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario
0583                   << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0584         QTest::newRow("item with non-existent tags (GID)")
0585             << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0586 
0587         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0588         updatePimItem(pimItem, QStringLiteral("TEST-16"), 0);
0589         updateTags(tags, {{QLatin1StringView("PLAIN"), QLatin1StringView("TAG-3")}, {QLatin1StringView("PLAIN"), QLatin1StringView("TAG-4")}});
0590         updateNotifcationEntity(notification, pimItem);
0591         ++uidnext;
0592         {
0593             auto cmd = createCommand(pimItem, datetime, {});
0594             cmd->setTags(Scope(Scope::Rid, {QLatin1StringView("TAG-3"), QLatin1StringView("TAG-4")}));
0595             inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd);
0596 
0597             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0598             rsp->setTags({Protocol::FetchTagsResponse(4, "TAG-3", "PLAIN"), Protocol::FetchTagsResponse(5, "TAG-4", "PLAIN")});
0599             outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp);
0600         }
0601         scenarios.clear();
0602         scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(QStringLiteral("akonadi_fake_resource_0")) << inScenario
0603                   << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0604         QTest::newRow("item with non-existent tags (RID)")
0605             << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0606 
0607         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0608         updatePimItem(pimItem, QStringLiteral("TEST-17"), 0);
0609         updateNotifcationEntity(notification, pimItem);
0610         updateTags(tags, {{QLatin1StringView("PLAIN"), QLatin1StringView("TAG-1")}, {QLatin1StringView("PLAIN"), QLatin1StringView("TAG-2")}});
0611         ++uidnext;
0612         {
0613             auto cmd = createCommand(pimItem, datetime, {});
0614             cmd->setTags(Scope(Scope::Rid, {QLatin1StringView("TAG-1"), QLatin1StringView("TAG-2")}));
0615             inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd);
0616 
0617             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0618             rsp->setTags({Protocol::FetchTagsResponse(2, "TAG-1", "PLAIN"), Protocol::FetchTagsResponse(3, "TAG-2", "PLAIN")});
0619             outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp);
0620         }
0621         scenarios.clear();
0622         scenarios << FakeAkonadiServer::loginScenario() << FakeAkonadiServer::selectResourceScenario(QStringLiteral("akonadi_fake_resource_0")) << inScenario
0623                   << outScenario << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0624         QTest::newRow("item with existing tags (RID)")
0625             << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0626 
0627         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0628         updatePimItem(pimItem, QStringLiteral("TEST-18"), 0);
0629         updateNotifcationEntity(notification, pimItem);
0630         updateTags(tags, {{QLatin1StringView("PLAIN"), QLatin1StringView("TAG-3")}, {QLatin1StringView("PLAIN"), QLatin1StringView("TAG-4")}});
0631         ++uidnext;
0632         {
0633             auto cmd = createCommand(pimItem, datetime, {});
0634             cmd->setTags(Scope(Scope::Gid, {QLatin1StringView("TAG-3"), QLatin1StringView("TAG-4")}));
0635             inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd);
0636 
0637             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0638             rsp->setTags({Protocol::FetchTagsResponse(4, "TAG-3", "PLAIN"), Protocol::FetchTagsResponse(5, "TAG-4", "PLAIN")});
0639             outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp);
0640         }
0641         scenarios.clear();
0642         scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario
0643                   << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0644         QTest::newRow("item with existing tags (GID)")
0645             << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0646 
0647         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0648         updatePimItem(pimItem, QStringLiteral("TEST-19"), 0);
0649         updateFlags(flags, QStringList() << QStringLiteral("\\SEEN") << QStringLiteral("$FLAG"));
0650         updateTags(tags, {{QLatin1StringView("PLAIN"), QLatin1StringView("TAG-1")}, {QLatin1StringView("PLAIN"), QLatin1StringView("TAG-2")}});
0651         updateNotifcationEntity(notification, pimItem);
0652         ++uidnext;
0653         {
0654             auto cmd = createCommand(pimItem, datetime, {});
0655             cmd->setTags(Scope(Scope::Gid, {QLatin1StringView("TAG-1"), QLatin1StringView("TAG-2")}));
0656             cmd->setFlags({"\\SEEN", "$FLAG"});
0657             inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd);
0658 
0659             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0660             rsp->setTags({Protocol::FetchTagsResponse(2, "TAG-1", "PLAIN"), Protocol::FetchTagsResponse(3, "TAG-2", "PLAIN")});
0661             rsp->setFlags({"\\SEEN", "$FLAG"});
0662             outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp);
0663         }
0664         scenarios.clear();
0665         scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario
0666                   << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0667         QTest::newRow("item with flags and tags") << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime
0668                                                   << false;
0669 
0670         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0671         updatePimItem(pimItem, QStringLiteral("TEST-20"), 0);
0672         updateFlags(flags, {});
0673         updateTags(tags, {{QLatin1StringView("PLAIN"), utf8String}});
0674         updateNotifcationEntity(notification, pimItem);
0675         ++uidnext;
0676         {
0677             auto cmd = createCommand(pimItem, datetime, {});
0678             cmd->setTags(Scope(Scope::Gid, {utf8String}));
0679             inScenario = TestScenario::create(5, TestScenario::ClientCmd, cmd);
0680 
0681             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0682             rsp->setTags({Protocol::FetchTagsResponse(6, utf8String.toUtf8(), "PLAIN")});
0683             outScenario = TestScenario::create(5, TestScenario::ServerCmd, rsp);
0684         }
0685         scenarios.clear();
0686         scenarios << FakeAkonadiServer::loginScenario() << inScenario << outScenario
0687                   << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0688         QTest::newRow("item with UTF-8 tag") << scenarios << Notifications{notification} << pimItem << parts << flags << tags << uidnext << datetime << false;
0689 
0690         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0691         updatePimItem(pimItem, QStringLiteral("TEST-21"), 0);
0692         updateFlags(flags, {});
0693         updateTags(tags, {});
0694         pimItem.setGid(QStringLiteral("GID-21"));
0695         updateNotifcationEntity(notification, pimItem);
0696         scenarios = FakeAkonadiServer::loginScenario();
0697         // Create a normal item with RID
0698         {
0699             ++uidnext;
0700             auto cmd = createCommand(pimItem, datetime, {});
0701             scenarios << TestScenario::create(5, TestScenario::ClientCmd, cmd);
0702             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0703             scenarios << TestScenario::create(5, TestScenario::ServerCmd, rsp)
0704                       << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0705         }
0706         // Create the same item again (no merging, so it will just be created)
0707         {
0708             ++uidnext;
0709             auto cmd = createCommand(pimItem, datetime, {});
0710             scenarios << TestScenario::create(6, TestScenario::ClientCmd, cmd);
0711             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0712             scenarios << TestScenario::create(6, TestScenario::ServerCmd, rsp)
0713                       << TestScenario::create(6, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0714         }
0715         // Now try to create the item once again, but in merge mode, we should fail now
0716         {
0717             ++uidnext;
0718             auto cmd = createCommand(pimItem, datetime, {});
0719             cmd->setMergeModes(Protocol::CreateItemCommand::RemoteID);
0720             scenarios << TestScenario::create(7, TestScenario::ClientCmd, cmd);
0721             auto rsp = Protocol::CreateItemResponsePtr::create();
0722             rsp->setError(1, QStringLiteral("Multiple merge candidates"));
0723             scenarios << TestScenario::create(7, TestScenario::ServerCmd, rsp);
0724         }
0725         Notifications notifications = {notification, Protocol::ItemChangeNotificationPtr::create(*notification)};
0726         QTest::newRow("multiple merge candidates (RID)") << scenarios << notifications << pimItem << parts << flags << tags << uidnext << datetime << true;
0727 
0728         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0729         updatePimItem(pimItem, QStringLiteral("TEST-22"), 0);
0730         pimItem.setGid(QStringLiteral("GID-22"));
0731         updateNotifcationEntity(notification, pimItem);
0732         scenarios = FakeAkonadiServer::loginScenario();
0733         // Create a normal item with GID
0734         {
0735             // Don't increase uidnext, we will reuse the one from previous test,
0736             // since that did not actually create a new Item
0737             auto cmd = createCommand(pimItem, datetime, {});
0738             scenarios << TestScenario::create(5, TestScenario::ClientCmd, cmd);
0739             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0740             scenarios << TestScenario::create(5, TestScenario::ServerCmd, rsp)
0741                       << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0742         }
0743         // Create the same item again (no merging, so it will just be created)
0744         {
0745             ++uidnext;
0746             auto cmd = createCommand(pimItem, datetime, {});
0747             scenarios << TestScenario::create(6, TestScenario::ClientCmd, cmd);
0748             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0749             scenarios << TestScenario::create(6, TestScenario::ServerCmd, rsp)
0750                       << TestScenario::create(6, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0751         }
0752         // Now try to create the item once again, but in merge mode, we should fail now
0753         {
0754             ++uidnext;
0755             auto cmd = createCommand(pimItem, datetime, {});
0756             cmd->setMergeModes(Protocol::CreateItemCommand::GID);
0757             scenarios << TestScenario::create(7, TestScenario::ClientCmd, cmd);
0758             auto rsp = Protocol::CreateItemResponsePtr::create();
0759             rsp->setError(1, QStringLiteral("Multiple merge candidates"));
0760             scenarios << TestScenario::create(7, TestScenario::ServerCmd, rsp);
0761         }
0762         notifications = {notification, Protocol::ItemChangeNotificationPtr::create(*notification)};
0763         QTest::newRow("multiple merge candidates (GID)") << scenarios << notifications << pimItem << parts << flags << tags << uidnext << datetime << true;
0764 
0765         notification = Protocol::ItemChangeNotificationPtr::create(*notification);
0766         updatePimItem(pimItem, QStringLiteral("TEST-23"), 0);
0767         pimItem.setGid(QString());
0768         updateNotifcationEntity(notification, pimItem);
0769         scenarios = FakeAkonadiServer::loginScenario();
0770         // Create a normal item with RID, but with empty GID
0771         {
0772             // Don't increase uidnext, we will reuse the one from previous test,
0773             // since that did not actually create a new Item
0774             auto cmd = createCommand(pimItem, datetime, {});
0775             scenarios << TestScenario::create(5, TestScenario::ClientCmd, cmd);
0776             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0777             scenarios << TestScenario::create(5, TestScenario::ServerCmd, rsp)
0778                       << TestScenario::create(5, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0779         }
0780         // Merge by GID - should not create a new Item but actually merge by RID,
0781         // since an item with matching RID but empty GID exists
0782         {
0783             ++uidnext;
0784             pimItem.setGid(QStringLiteral("GID-23"));
0785             auto cmd = createCommand(pimItem, datetime, {});
0786             cmd->setMergeModes(Protocol::CreateItemCommand::GID);
0787             scenarios << TestScenario::create(6, TestScenario::ClientCmd, cmd);
0788             auto rsp = createResponse(uidnext, pimItem, datetime, {});
0789             scenarios << TestScenario::create(6, TestScenario::ServerCmd, rsp)
0790                       << TestScenario::create(6, TestScenario::ServerCmd, Protocol::CreateItemResponsePtr::create());
0791         }
0792         notifications = {notification, Protocol::ItemChangeNotificationPtr::create(*notification)};
0793         QTest::newRow("merge into empty GID if RID matches") << scenarios << notifications << pimItem << parts << flags << tags << uidnext << datetime << false;
0794     }
0795 
0796     void testItemCreate()
0797     {
0798         QFETCH(TestScenario::List, scenarios);
0799         QFETCH(QList<Protocol::ItemChangeNotificationPtr>, notifications);
0800         QFETCH(PimItem, pimItem);
0801         QFETCH(QList<FakePart>, parts);
0802         QFETCH(QList<Flag>, flags);
0803         QFETCH(QList<FakeTag>, tags);
0804         QFETCH(qint64, uidnext);
0805         QFETCH(bool, expectFail);
0806 
0807         mAkonadi.setScenarios(scenarios);
0808         mAkonadi.runTest();
0809 
0810         auto notificationSpy = mAkonadi.notificationSpy();
0811 
0812         QTRY_COMPARE(notificationSpy->count(), notifications.count());
0813         for (int i = 0; i < notifications.count(); ++i) {
0814             const auto incomingNtfs = notificationSpy->at(i).first().value<Protocol::ChangeNotificationList>();
0815             QCOMPARE(incomingNtfs.count(), 1);
0816             const auto itemNotification = incomingNtfs.at(0).staticCast<Protocol::ItemChangeNotification>();
0817 
0818             QVERIFY(AkTest::compareNotifications(itemNotification, notifications.at(i), QFlag(AkTest::NtfAll & ~AkTest::NtfEntities)));
0819             QCOMPARE(itemNotification->items().count(), notifications.at(i)->items().count());
0820         }
0821 
0822         const PimItem actualItem = PimItem::retrieveById(uidnext);
0823         if (expectFail) {
0824             QVERIFY(!actualItem.isValid());
0825         } else {
0826             QVERIFY(actualItem.isValid());
0827             QCOMPARE(actualItem.remoteId(), pimItem.remoteId());
0828             QCOMPARE(actualItem.remoteRevision(), pimItem.remoteRevision());
0829             QCOMPARE(actualItem.gid(), pimItem.gid());
0830             QCOMPARE(actualItem.size(), pimItem.size());
0831             QCOMPARE(actualItem.datetime(), pimItem.datetime());
0832             QCOMPARE(actualItem.collectionId(), pimItem.collectionId());
0833             QCOMPARE(actualItem.mimeTypeId(), pimItem.mimeTypeId());
0834 
0835             const auto actualFlags = actualItem.flags() | AkRanges::Actions::toQList;
0836             QCOMPARE(actualFlags.count(), flags.count());
0837             for (const Flag &flag : std::as_const(flags)) {
0838                 const auto actualFlagIter = std::find_if(actualFlags.constBegin(), actualFlags.constEnd(), [flag](Flag const &actualFlag) {
0839                     return flag.name() == actualFlag.name();
0840                 });
0841                 QVERIFY(actualFlagIter != actualFlags.constEnd());
0842                 const Flag actualFlag = *actualFlagIter;
0843                 QVERIFY(actualFlag.isValid());
0844             }
0845 
0846             const auto actualTags = actualItem.tags() | AkRanges::Actions::toQList;
0847             QCOMPARE(actualTags.count(), tags.count());
0848             for (const FakeTag &tag : std::as_const(tags)) {
0849                 const auto actualTagIter = std::find_if(actualTags.constBegin(), actualTags.constEnd(), [tag](Tag const &actualTag) {
0850                     return tag.gid() == actualTag.gid();
0851                 });
0852 
0853                 QVERIFY(actualTagIter != actualTags.constEnd());
0854                 const Tag actualTag = *actualTagIter;
0855                 QVERIFY(actualTag.isValid());
0856                 QCOMPARE(actualTag.tagType().name(), tag.tagType().name());
0857                 QCOMPARE(actualTag.gid(), tag.gid());
0858                 if (!tag.remoteId().isEmpty()) {
0859                     SelectQueryBuilder<TagRemoteIdResourceRelation> qb;
0860                     qb.addValueCondition(TagRemoteIdResourceRelation::resourceIdFullColumnName(), Query::Equals, QLatin1StringView("akonadi_fake_resource_0"));
0861                     qb.addValueCondition(TagRemoteIdResourceRelation::tagIdColumn(), Query::Equals, actualTag.id());
0862                     QVERIFY(qb.exec());
0863                     QCOMPARE(qb.result().size(), 1);
0864                     QCOMPARE(qb.result().at(0).remoteId(), tag.remoteId());
0865                 }
0866             }
0867 
0868             const auto actualParts = actualItem.parts() | AkRanges::Actions::toQList;
0869             QCOMPARE(actualParts.count(), parts.count());
0870             for (const FakePart &part : std::as_const(parts)) {
0871                 const auto actualPartIter = std::find_if(actualParts.constBegin(), actualParts.constEnd(), [part](Part const &actualPart) {
0872                     return part.partType().ns() == actualPart.partType().ns() && part.partType().name() == actualPart.partType().name();
0873                 });
0874 
0875                 QVERIFY(actualPartIter != actualParts.constEnd());
0876                 const Part actualPart = *actualPartIter;
0877                 QVERIFY(actualPart.isValid());
0878                 QCOMPARE(QString::fromUtf8(actualPart.data()), QString::fromUtf8(part.data()));
0879                 QCOMPARE(actualPart.data(), part.data());
0880                 QCOMPARE(actualPart.datasize(), part.datasize());
0881                 QCOMPARE(actualPart.storage(), part.storage());
0882             }
0883         }
0884     }
0885 };
0886 
0887 AKTEST_FAKESERVER_MAIN(ItemCreateHandlerTest)
0888 
0889 #include "itemcreatehandlertest.moc"