File indexing completed on 2024-12-22 04:57:36

0001 /*  This file is part of the KDE project
0002     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
0003     SPDX-FileContributor: Kevin Krammer <krake@kdab.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "mixedmaildirstore.h"
0009 
0010 #include "testdatautil.h"
0011 
0012 #include "filestore/itemcreatejob.h"
0013 #include "filestore/itemfetchjob.h"
0014 
0015 #include "libmaildir/maildir.h"
0016 
0017 #include <KMbox/MBox>
0018 
0019 #include <KMime/Message>
0020 
0021 #include <QRandomGenerator>
0022 #include <QTemporaryDir>
0023 
0024 #include <QDir>
0025 #include <QFileInfo>
0026 #include <QTest>
0027 
0028 using namespace Akonadi;
0029 
0030 class ItemCreateTest : public QObject
0031 {
0032     Q_OBJECT
0033 
0034 public:
0035     ItemCreateTest()
0036         : QObject()
0037         , mStore(nullptr)
0038         , mDir(nullptr)
0039     {
0040     }
0041 
0042     ~ItemCreateTest() override
0043     {
0044         delete mStore;
0045         delete mDir;
0046     }
0047 
0048 private:
0049     MixedMaildirStore *mStore = nullptr;
0050     QTemporaryDir *mDir = nullptr;
0051 
0052 private Q_SLOTS:
0053     void init();
0054     void cleanup();
0055     void testExpectedFail();
0056     void testMBox();
0057     void testMaildir();
0058 };
0059 
0060 void ItemCreateTest::init()
0061 {
0062     mStore = new MixedMaildirStore;
0063 
0064     mDir = new QTemporaryDir;
0065     QVERIFY(mDir->isValid());
0066     QVERIFY(QDir(mDir->path()).exists());
0067 }
0068 
0069 void ItemCreateTest::cleanup()
0070 {
0071     delete mStore;
0072     mStore = nullptr;
0073     delete mDir;
0074     mDir = nullptr;
0075 }
0076 
0077 void ItemCreateTest::testExpectedFail()
0078 {
0079     QDir topDir(mDir->path());
0080 
0081     QVERIFY(TestDataUtil::installFolder(QLatin1StringView("maildir"), topDir.path(), QStringLiteral("data")));
0082     QDir dataDir = topDir;
0083     QVERIFY(dataDir.cd(QLatin1StringView("data")));
0084     KPIM::Maildir dataMd(dataDir.path(), false);
0085     QVERIFY(dataMd.isValid());
0086 
0087     const QStringList dataEntryList = dataMd.entryList();
0088     QCOMPARE(dataEntryList.count(), 4);
0089     KMime::Message::Ptr msgPtr(new KMime::Message);
0090     msgPtr->setContent(KMime::CRLFtoLF(dataMd.readEntry(dataEntryList.first())));
0091 
0092     QVERIFY(topDir.mkdir(QLatin1StringView("store")));
0093     QVERIFY(topDir.cd(QLatin1StringView("store")));
0094     mStore->setPath(topDir.path());
0095 
0096     FileStore::ItemCreateJob *job = nullptr;
0097 
0098     // test failure of adding item to top level collection
0099     Item item;
0100     item.setMimeType(KMime::Message::mimeType());
0101     item.setPayload<KMime::Message::Ptr>(msgPtr);
0102 
0103     job = mStore->createItem(item, mStore->topLevelCollection());
0104 
0105     QVERIFY(!job->exec());
0106     QCOMPARE(job->error(), (int)FileStore::Job::InvalidJobContext);
0107 
0108     // test failure of adding item to non existent collection
0109     Collection collection;
0110     collection.setName(QStringLiteral("collection"));
0111     collection.setRemoteId(QStringLiteral("collection"));
0112     collection.setParentCollection(mStore->topLevelCollection());
0113 
0114     job = mStore->createItem(item, collection);
0115 
0116     QVERIFY(!job->exec());
0117     QCOMPARE(job->error(), (int)FileStore::Job::InvalidJobContext);
0118 }
0119 
0120 void ItemCreateTest::testMBox()
0121 {
0122     QDir topDir(mDir->path());
0123 
0124     QVERIFY(TestDataUtil::installFolder(QLatin1StringView("maildir"), topDir.path(), QStringLiteral("data")));
0125     QDir dataDir = topDir;
0126     QVERIFY(dataDir.cd(QLatin1StringView("data")));
0127     KPIM::Maildir dataMd(dataDir.path(), false);
0128     QVERIFY(dataMd.isValid());
0129 
0130     const QStringList dataEntryList = dataMd.entryList();
0131     QCOMPARE(dataEntryList.count(), 4);
0132     KMime::Message::Ptr msgPtr1(new KMime::Message);
0133     msgPtr1->setContent(KMime::CRLFtoLF(dataMd.readEntry(dataEntryList.first())));
0134     KMime::Message::Ptr msgPtr2(new KMime::Message);
0135     msgPtr2->setContent(KMime::CRLFtoLF(dataMd.readEntry(dataEntryList.last())));
0136 
0137     QVERIFY(topDir.mkdir(QLatin1StringView("store")));
0138     QVERIFY(topDir.cd(QLatin1StringView("store")));
0139 
0140     QVERIFY(TestDataUtil::installFolder(QLatin1StringView("mbox"), topDir.path(), QStringLiteral("collection1")));
0141 
0142     QFileInfo fileInfo1(topDir.path(), QStringLiteral("collection1"));
0143     KMBox::MBox mbox1;
0144     QVERIFY(mbox1.load(fileInfo1.absoluteFilePath()));
0145     QCOMPARE((int)mbox1.entries().count(), 4);
0146     const int size1 = fileInfo1.size();
0147 
0148     // simulate empty mbox
0149     QFileInfo fileInfo2(topDir.path(), QStringLiteral("collection2"));
0150     QFile file2(fileInfo2.absoluteFilePath());
0151     QVERIFY(file2.open(QIODevice::WriteOnly));
0152     file2.close();
0153     QVERIFY(file2.exists());
0154     QCOMPARE((int)file2.size(), 0);
0155 
0156     mStore->setPath(topDir.path());
0157 
0158     // common variables
0159     const QVariant colListVar = QVariant::fromValue<Collection::List>(Collection::List());
0160     QVariant var;
0161     Collection::List collections;
0162     Item::List items;
0163     QMap<QByteArray, int> flagCounts;
0164 
0165     FileStore::ItemCreateJob *job = nullptr;
0166     FileStore::ItemFetchJob *itemFetch = nullptr;
0167 
0168     // test adding to empty mbox
0169     Collection collection2;
0170     collection2.setName(QStringLiteral("collection2"));
0171     collection2.setRemoteId(QStringLiteral("collection2"));
0172     collection2.setParentCollection(mStore->topLevelCollection());
0173 
0174     Item item1;
0175     item1.setId(QRandomGenerator::global()->generate());
0176     item1.setMimeType(KMime::Message::mimeType());
0177     item1.setPayload<KMime::Message::Ptr>(msgPtr1);
0178 
0179     job = mStore->createItem(item1, collection2);
0180 
0181     QVERIFY(job->exec());
0182     QCOMPARE(job->error(), 0);
0183 
0184     Item item = job->item();
0185     QCOMPARE(item.id(), item1.id());
0186     QVERIFY(!item.remoteId().isEmpty());
0187     QCOMPARE(item.remoteId(), QStringLiteral("0"));
0188     QCOMPARE(item.parentCollection(), collection2);
0189 
0190     fileInfo2.refresh();
0191     QVERIFY(fileInfo2.size() > 0);
0192     const int size2 = fileInfo2.size();
0193 
0194     KMBox::MBox mbox2;
0195     QVERIFY(mbox2.load(fileInfo2.absoluteFilePath()));
0196     QCOMPARE((int)mbox2.entries().count(), 1);
0197 
0198     Item item2;
0199     item2.setId(QRandomGenerator::global()->generate());
0200     item2.setMimeType(KMime::Message::mimeType());
0201     item2.setPayload<KMime::Message::Ptr>(msgPtr2);
0202 
0203     job = mStore->createItem(item2, collection2);
0204 
0205     QVERIFY(job->exec());
0206     QCOMPARE(job->error(), 0);
0207 
0208     item = job->item();
0209     QCOMPARE(item.id(), item2.id());
0210     QVERIFY(!item.remoteId().isEmpty());
0211     QCOMPARE(item.remoteId(), QString::number(size2 + 1));
0212     QCOMPARE(item.parentCollection(), collection2);
0213 
0214     fileInfo2.refresh();
0215     QVERIFY(fileInfo2.size() > 0);
0216 
0217     QVERIFY(mbox2.load(fileInfo2.absoluteFilePath()));
0218     QCOMPARE((int)mbox2.entries().count(), 2);
0219 
0220     // test adding to non-empty mbox
0221     Collection collection1;
0222     collection1.setName(QStringLiteral("collection1"));
0223     collection1.setRemoteId(QStringLiteral("collection1"));
0224     collection1.setParentCollection(mStore->topLevelCollection());
0225 
0226     job = mStore->createItem(item1, collection1);
0227 
0228     QVERIFY(job->exec());
0229     QCOMPARE(job->error(), 0);
0230 
0231     item = job->item();
0232     QCOMPARE(item.id(), item1.id());
0233     QVERIFY(!item.remoteId().isEmpty());
0234     QCOMPARE(item.remoteId(), QString::number(size1 + 1));
0235     QCOMPARE(item.parentCollection(), collection1);
0236 
0237     fileInfo1.refresh();
0238     QVERIFY(fileInfo1.size() > size1);
0239 
0240     QVERIFY(mbox1.load(fileInfo1.absoluteFilePath()));
0241     QCOMPARE((int)mbox1.entries().count(), 5);
0242 
0243     // check for index preservation
0244     var = job->property("onDiskIndexInvalidated");
0245     QVERIFY(var.isValid());
0246     QCOMPARE(var.userType(), colListVar.userType());
0247 
0248     collections = var.value<Collection::List>();
0249     QCOMPARE((int)collections.count(), 1);
0250     QCOMPARE(collections.first(), collection1);
0251 
0252     // get the items and check the flags (see data/README)
0253     itemFetch = mStore->fetchItems(collection1);
0254     QVERIFY(itemFetch->exec());
0255     QCOMPARE(itemFetch->error(), 0);
0256 
0257     items = itemFetch->items();
0258     QCOMPARE((int)items.count(), 5);
0259     for (const Item &item : std::as_const(items)) {
0260         const auto flags{item.flags()};
0261         for (const QByteArray &flag : flags) {
0262             ++flagCounts[flag];
0263         }
0264     }
0265 
0266     QCOMPARE(flagCounts["\\SEEN"], 2);
0267     QCOMPARE(flagCounts["\\FLAGGED"], 1);
0268     QCOMPARE(flagCounts["$TODO"], 1);
0269     flagCounts.clear();
0270 
0271     job = mStore->createItem(item2, collection1);
0272 
0273     QVERIFY(job->exec());
0274     QCOMPARE(job->error(), 0);
0275 
0276     item = job->item();
0277     QCOMPARE(item.id(), item2.id());
0278     QVERIFY(!item.remoteId().isEmpty());
0279     QCOMPARE(item.remoteId(), QString::number(size1 + 1 + size2 + 1));
0280     QCOMPARE(item.parentCollection(), collection1);
0281 
0282     fileInfo1.refresh();
0283     QVERIFY(fileInfo1.size() > (size1 + size2));
0284 
0285     QVERIFY(mbox1.load(fileInfo1.absoluteFilePath()));
0286     QCOMPARE((int)mbox1.entries().count(), 6);
0287 
0288     // check for index preservation
0289     var = job->property("onDiskIndexInvalidated");
0290     QVERIFY(var.isValid());
0291     QCOMPARE(var.userType(), colListVar.userType());
0292 
0293     collections = var.value<Collection::List>();
0294     QCOMPARE((int)collections.count(), 1);
0295     QCOMPARE(collections.first(), collection1);
0296 
0297     // get the items and check the flags (see data/README)
0298     itemFetch = mStore->fetchItems(collection1);
0299     QVERIFY(itemFetch->exec());
0300     QCOMPARE(itemFetch->error(), 0);
0301 
0302     items = itemFetch->items();
0303     QCOMPARE((int)items.count(), 6);
0304     for (const Item &item : std::as_const(items)) {
0305         const auto flags{item.flags()};
0306         for (const QByteArray &flag : flags) {
0307             ++flagCounts[flag];
0308         }
0309     }
0310 
0311     QCOMPARE(flagCounts["\\SEEN"], 2);
0312     QCOMPARE(flagCounts["\\FLAGGED"], 1);
0313     QCOMPARE(flagCounts["$TODO"], 1);
0314     flagCounts.clear();
0315 }
0316 
0317 void ItemCreateTest::testMaildir()
0318 {
0319     QDir topDir(mDir->path());
0320 
0321     QVERIFY(TestDataUtil::installFolder(QLatin1StringView("maildir"), topDir.path(), QStringLiteral("data")));
0322     QDir dataDir = topDir;
0323     QVERIFY(dataDir.cd(QLatin1StringView("data")));
0324     KPIM::Maildir dataMd(dataDir.path(), false);
0325     QVERIFY(dataMd.isValid());
0326 
0327     const QStringList dataEntryList = dataMd.entryList();
0328     QCOMPARE(dataEntryList.count(), 4);
0329     KMime::Message::Ptr msgPtr1(new KMime::Message);
0330     msgPtr1->setContent(KMime::CRLFtoLF(dataMd.readEntry(dataEntryList.first())));
0331     KMime::Message::Ptr msgPtr2(new KMime::Message);
0332     msgPtr2->setContent(KMime::CRLFtoLF(dataMd.readEntry(dataEntryList.last())));
0333 
0334     QVERIFY(topDir.mkdir(QLatin1StringView("store")));
0335     QVERIFY(topDir.cd(QLatin1StringView("store")));
0336 
0337     QVERIFY(TestDataUtil::installFolder(QLatin1StringView("maildir"), topDir.path(), QStringLiteral("collection1")));
0338 
0339     KPIM::Maildir topLevelMd(topDir.path(), true);
0340     KPIM::Maildir md1 = topLevelMd.subFolder(QStringLiteral("collection1"));
0341     QVERIFY(md1.isValid());
0342 
0343     const QStringList md1EntryList = md1.entryList();
0344     QSet<QString> entrySet1(md1EntryList.cbegin(), md1EntryList.cend());
0345     QCOMPARE((int)entrySet1.count(), 4);
0346 
0347     // simulate empty maildir
0348     KPIM::Maildir md2(topLevelMd.addSubFolder(QStringLiteral("collection2")), false);
0349     QVERIFY(md2.isValid());
0350 
0351     const QStringList md2EntryList = md2.entryList();
0352     QSet<QString> entrySet2(md2EntryList.cbegin(), md2EntryList.cend());
0353     QCOMPARE((int)entrySet2.count(), 0);
0354 
0355     mStore->setPath(topDir.path());
0356 
0357     // common variables
0358     const QVariant colListVar = QVariant::fromValue<Collection::List>(Collection::List());
0359     QVariant var;
0360     Collection::List collections;
0361     Item::List items;
0362     QMap<QByteArray, int> flagCounts;
0363 
0364     QStringList entryList;
0365     QSet<QString> entrySet;
0366     QSet<QString> newIdSet;
0367     QString newId;
0368 
0369     FileStore::ItemCreateJob *job = nullptr;
0370     FileStore::ItemFetchJob *itemFetch = nullptr;
0371 
0372     // test adding to empty maildir
0373     Collection collection2;
0374     collection2.setName(QStringLiteral("collection2"));
0375     collection2.setRemoteId(QStringLiteral("collection2"));
0376     collection2.setParentCollection(mStore->topLevelCollection());
0377 
0378     Item item1;
0379     item1.setId(QRandomGenerator::global()->generate());
0380     item1.setMimeType(KMime::Message::mimeType());
0381     item1.setPayload<KMime::Message::Ptr>(msgPtr1);
0382 
0383     job = mStore->createItem(item1, collection2);
0384 
0385     QVERIFY(job->exec());
0386     QCOMPARE(job->error(), 0);
0387 
0388     Item item = job->item();
0389     QCOMPARE(item.id(), item1.id());
0390     QVERIFY(!item.remoteId().isEmpty());
0391     QCOMPARE(item.parentCollection(), collection2);
0392 
0393     entryList = md2.entryList();
0394     entrySet = QSet<QString>(entryList.cbegin(), entryList.cend());
0395     QCOMPARE((int)entrySet.count(), 1);
0396 
0397     newIdSet = entrySet.subtract(entrySet2);
0398     QCOMPARE((int)newIdSet.count(), 1);
0399 
0400     newId = *newIdSet.cbegin();
0401     QCOMPARE(item.remoteId(), newId);
0402     entrySet2 << newId;
0403     QCOMPARE((int)entrySet2.count(), 1);
0404 
0405     Item item2;
0406     item2.setId(QRandomGenerator::global()->generate());
0407     item2.setMimeType(KMime::Message::mimeType());
0408     item2.setPayload<KMime::Message::Ptr>(msgPtr2);
0409 
0410     job = mStore->createItem(item2, collection2);
0411 
0412     QVERIFY(job->exec());
0413     QCOMPARE(job->error(), 0);
0414 
0415     item = job->item();
0416     QCOMPARE(item.id(), item2.id());
0417     QVERIFY(!item.remoteId().isEmpty());
0418     QCOMPARE(item.parentCollection(), collection2);
0419 
0420     entryList = md2.entryList();
0421     entrySet = QSet<QString>(entryList.cbegin(), entryList.cend());
0422     QCOMPARE((int)entrySet.count(), 2);
0423 
0424     newIdSet = entrySet.subtract(entrySet2);
0425     QCOMPARE((int)newIdSet.count(), 1);
0426 
0427     newId = *newIdSet.cbegin();
0428     QCOMPARE(item.remoteId(), newId);
0429     entrySet2 << newId;
0430     QCOMPARE((int)entrySet2.count(), 2);
0431 
0432     // test adding to non-empty maildir
0433     Collection collection1;
0434     collection1.setName(QStringLiteral("collection1"));
0435     collection1.setRemoteId(QStringLiteral("collection1"));
0436     collection1.setParentCollection(mStore->topLevelCollection());
0437 
0438     job = mStore->createItem(item1, collection1);
0439 
0440     QVERIFY(job->exec());
0441     QCOMPARE(job->error(), 0);
0442 
0443     item = job->item();
0444     QCOMPARE(item.id(), item1.id());
0445     QVERIFY(!item.remoteId().isEmpty());
0446     QCOMPARE(item.parentCollection(), collection1);
0447 
0448     entryList = md1.entryList();
0449     entrySet = QSet<QString>(entryList.cbegin(), entryList.cend());
0450     QCOMPARE((int)entrySet.count(), 5);
0451 
0452     newIdSet = entrySet.subtract(entrySet1);
0453     QCOMPARE((int)newIdSet.count(), 1);
0454 
0455     newId = *newIdSet.cbegin();
0456     QCOMPARE(item.remoteId(), newId);
0457     entrySet1 << newId;
0458     QCOMPARE((int)entrySet1.count(), 5);
0459 
0460     // check for index preservation
0461     var = job->property("onDiskIndexInvalidated");
0462     QVERIFY(var.isValid());
0463     QCOMPARE(var.userType(), colListVar.userType());
0464 
0465     collections = var.value<Collection::List>();
0466     QCOMPARE((int)collections.count(), 1);
0467     QCOMPARE(collections.first(), collection1);
0468 
0469     // get the items and check the flags (see data/README)
0470     itemFetch = mStore->fetchItems(collection1);
0471     QVERIFY(itemFetch->exec());
0472     QCOMPARE(itemFetch->error(), 0);
0473 
0474     items = itemFetch->items();
0475     QCOMPARE((int)items.count(), 5);
0476     for (const Item &item : std::as_const(items)) {
0477         const auto flags{item.flags()};
0478         for (const QByteArray &flag : flags) {
0479             ++flagCounts[flag];
0480         }
0481     }
0482 
0483     QCOMPARE(flagCounts["\\SEEN"], 2);
0484     QCOMPARE(flagCounts["\\FLAGGED"], 1);
0485     QCOMPARE(flagCounts["$TODO"], 1);
0486     flagCounts.clear();
0487 
0488     job = mStore->createItem(item2, collection1);
0489 
0490     QVERIFY(job->exec());
0491     QCOMPARE(job->error(), 0);
0492 
0493     item = job->item();
0494     QCOMPARE(item.id(), item2.id());
0495     QVERIFY(!item.remoteId().isEmpty());
0496     QCOMPARE(item.parentCollection(), collection1);
0497 
0498     entryList = md1.entryList();
0499     entrySet = QSet<QString>(entryList.cbegin(), entryList.cend());
0500     QCOMPARE((int)entrySet.count(), 6);
0501 
0502     newIdSet = entrySet.subtract(entrySet1);
0503     QCOMPARE((int)newIdSet.count(), 1);
0504 
0505     newId = *newIdSet.cbegin();
0506     QCOMPARE(item.remoteId(), newId);
0507     entrySet1 << newId;
0508     QCOMPARE((int)entrySet1.count(), 6);
0509 
0510     // check for index preservation
0511     var = job->property("onDiskIndexInvalidated");
0512     QVERIFY(var.isValid());
0513     QCOMPARE(var.userType(), colListVar.userType());
0514 
0515     collections = var.value<Collection::List>();
0516     QCOMPARE((int)collections.count(), 1);
0517     QCOMPARE(collections.first(), collection1);
0518 
0519     // get the items and check the flags (see data/README)
0520     itemFetch = mStore->fetchItems(collection1);
0521     QVERIFY(itemFetch->exec());
0522     QCOMPARE(itemFetch->error(), 0);
0523 
0524     items = itemFetch->items();
0525     QCOMPARE((int)items.count(), 6);
0526     for (const Item &item : std::as_const(items)) {
0527         const auto flags{item.flags()};
0528         for (const QByteArray &flag : flags) {
0529             ++flagCounts[flag];
0530         }
0531     }
0532 
0533     QCOMPARE(flagCounts["\\SEEN"], 2);
0534     QCOMPARE(flagCounts["\\FLAGGED"], 1);
0535     QCOMPARE(flagCounts["$TODO"], 1);
0536     flagCounts.clear();
0537 }
0538 
0539 QTEST_MAIN(ItemCreateTest)
0540 
0541 #include "itemcreatetest.moc"