Warning, file /pim/sink/tests/mailthreadtest.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *   Copyright (C) 2016 Christian Mollekopf <chrigi_1@fastmail.fm>
0003  *
0004  *   This program is free software; you can redistribute it and/or modify
0005  *   it under the terms of the GNU General Public License as published by
0006  *   the Free Software Foundation; either version 2 of the License, or
0007  *   (at your option) any later version.
0008  *
0009  *   This program is distributed in the hope that it will be useful,
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012  *   GNU General Public License for more details.
0013  *
0014  *   You should have received a copy of the GNU General Public License
0015  *   along with this program; if not, write to the
0016  *   Free Software Foundation, Inc.,
0017  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
0018  */
0019 #include "mailthreadtest.h"
0020 
0021 #include <QTest>
0022 
0023 #include <QString>
0024 #include <QFile>
0025 #include <KMime/Message>
0026 
0027 #include "store.h"
0028 #include "resourcecontrol.h"
0029 #include "log.h"
0030 #include "test.h"
0031 #include "standardqueries.h"
0032 #include "index.h"
0033 #include "definitions.h"
0034 
0035 using namespace Sink;
0036 using namespace Sink::ApplicationDomain;
0037 
0038 //TODO extract resource test
0039 //
0040 void MailThreadTest::initTestCase()
0041 {
0042     Test::initTest();
0043     QVERIFY(isBackendAvailable());
0044     resetTestEnvironment();
0045     auto resource = createResource();
0046     QVERIFY(!resource.identifier().isEmpty());
0047 
0048     VERIFYEXEC(Store::create(resource));
0049 
0050     mResourceInstanceIdentifier = resource.identifier();
0051     mCapabilities = resource.getProperty("capabilities").value<QByteArrayList>();
0052 }
0053 
0054 void MailThreadTest::cleanup()
0055 {
0056     VERIFYEXEC(ResourceControl::shutdown(mResourceInstanceIdentifier));
0057     removeResourceFromDisk(mResourceInstanceIdentifier);
0058 }
0059 
0060 void MailThreadTest::init()
0061 {
0062     VERIFYEXEC(ResourceControl::start(mResourceInstanceIdentifier));
0063 }
0064 
0065 
0066 void MailThreadTest::testListThreadLeader()
0067 {
0068     Sink::Query query;
0069     query.resourceFilter(mResourceInstanceIdentifier);
0070     query.request<Mail::Subject>().request<Mail::MimeMessage>().request<Mail::Folder>().request<Mail::Date>();
0071     query.sort<Mail::Date>();
0072     query.reduce<Mail::ThreadId>(Query::Reduce::Selector::max<Mail::Date>()).count("count").collect<Mail::Sender>("senders");
0073 
0074     // Ensure all local data is processed
0075     VERIFYEXEC(Store::synchronize(query));
0076     VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
0077 
0078     auto mails = Store::read<Mail>(query);
0079     QCOMPARE(mails.size(), 1);
0080     QVERIFY(mails.first().getSubject().startsWith(QString("ThreadLeader")));
0081     auto threadSize = mails.first().getProperty("count").toInt();
0082     QCOMPARE(threadSize, 2);
0083     QCOMPARE(mails.first().aggregatedIds().size(), 2);
0084 }
0085 
0086 /*
0087  * Thread:
0088  * 1.
0089  *  2.
0090  *   3.
0091  *
0092  * 3. first, should result in a new thread.
0093  * 1. second, should be merged by subject
0094  * 2. last, should complete the thread.
0095  */
0096 void MailThreadTest::testIndexInMixedOrder()
0097 {
0098     auto folder = Folder::create(mResourceInstanceIdentifier);
0099     folder.setName("folder");
0100     VERIFYEXEC(Store::create(folder));
0101 
0102     auto message1 = KMime::Message::Ptr::create();
0103     message1->subject(true)->fromUnicodeString("1", "utf8");
0104     message1->messageID(true)->generate("foobar.com");
0105     message1->date(true)->setDateTime(QDateTime::currentDateTimeUtc());
0106     message1->assemble();
0107 
0108     auto message2 = KMime::Message::Ptr::create();
0109     message2->subject(true)->fromUnicodeString("Re: 1", "utf8");
0110     message2->messageID(true)->generate("foobar.com");
0111     message2->inReplyTo(true)->appendIdentifier(message1->messageID(true)->identifier());
0112     message2->date(true)->setDateTime(QDateTime::currentDateTimeUtc().addSecs(1));
0113     message2->assemble();
0114 
0115     auto message3 = KMime::Message::Ptr::create();
0116     message3->subject(true)->fromUnicodeString("Re: Re: 1", "utf8");
0117     message3->messageID(true)->generate("foobar.com");
0118     message3->inReplyTo(true)->appendIdentifier(message2->messageID(true)->identifier());
0119     message3->date(true)->setDateTime(QDateTime::currentDateTimeUtc().addSecs(2));
0120     message3->assemble();
0121 
0122     {
0123         auto mail = Mail::create(mResourceInstanceIdentifier);
0124         mail.setMimeMessage(message3->encodedContent(true));
0125         mail.setFolder(folder);
0126         VERIFYEXEC(Store::create(mail));
0127     }
0128     VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
0129 
0130     auto query = Sink::StandardQueries::threadLeaders(folder);
0131     query.resourceFilter(mResourceInstanceIdentifier);
0132     query.request<Mail::Subject>().request<Mail::MimeMessage>().request<Mail::Folder>().request<Mail::Date>();
0133 
0134     Mail threadLeader;
0135 
0136     //Ensure we find the thread leader
0137     {
0138         auto mails = Store::read<Mail>(query);
0139         QCOMPARE(mails.size(), 1);
0140         auto mail = mails.first();
0141         threadLeader = mail;
0142         QCOMPARE(mail.getSubject(), QString::fromLatin1("Re: Re: 1"));
0143     }
0144 
0145     {
0146         auto mail = Mail::create(mResourceInstanceIdentifier);
0147         mail.setMimeMessage(message2->encodedContent(true));
0148         mail.setFolder(folder);
0149         VERIFYEXEC(Store::create(mail));
0150     }
0151     VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
0152 
0153     //Ensure we find the thread leader still
0154     {
0155         auto mails = Store::read<Mail>(query);
0156         QCOMPARE(mails.size(), 1);
0157         auto mail = mails.first();
0158         QCOMPARE(mail.getSubject(), QString::fromLatin1("Re: Re: 1"));
0159     }
0160 
0161     {
0162         auto mail = Mail::create(mResourceInstanceIdentifier);
0163         mail.setMimeMessage(message1->encodedContent(true));
0164         mail.setFolder(folder);
0165         VERIFYEXEC(Store::create(mail));
0166     }
0167     VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
0168 
0169     //Ensure the thread is complete
0170     {
0171         auto query = Sink::StandardQueries::completeThread(threadLeader);
0172         query.request<Mail::Subject>().request<Mail::MimeMessage>().request<Mail::Folder>().request<Mail::Date>();
0173 
0174         auto mails = Store::read<Mail>(query);
0175         QCOMPARE(mails.size(), 3);
0176         auto mail = mails.first();
0177         QCOMPARE(mail.getSubject(), QString::fromLatin1("Re: Re: 1"));
0178     }
0179 
0180     /* VERIFYEXEC(Store::remove(mail)); */
0181     /* VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier)); */
0182     /* { */
0183     /*     auto job = Store::fetchAll<Mail>(Query::RequestedProperties(QByteArrayList() << Mail::Folder::name << Mail::Subject::name)) */
0184     /*         .then([=](const QList<Mail::Ptr> &mails) { */
0185     /*             QCOMPARE(mails.size(), 0); */
0186     /*         }); */
0187     /*     VERIFYEXEC(job); */
0188     /* } */
0189     /* VERIFYEXEC(ResourceControl::flushReplayQueue(QByteArrayList() << mResourceInstanceIdentifier)); */
0190 }
0191 
0192 static QByteArray readMailFromFile(const QString &mailFile)
0193 {
0194     QFile file(QLatin1String(THREADTESTDATAPATH) + QLatin1Char('/') + mailFile);
0195     file.open(QIODevice::ReadOnly);
0196     Q_ASSERT(file.isOpen());
0197     return file.readAll();
0198 }
0199 
0200 static KMime::Message::Ptr readMail(const QString &mailFile)
0201 {
0202     auto msg = KMime::Message::Ptr::create();
0203     msg->setContent(readMailFromFile(mailFile));
0204     msg->parse();
0205     return msg;
0206 }
0207 
0208 void MailThreadTest::testRealWorldThread()
0209 {
0210     auto folder = Folder::create(mResourceInstanceIdentifier);
0211     folder.setName("folder");
0212     VERIFYEXEC(Store::create(folder));
0213 
0214     auto createMail = [this, folder] (KMime::Message::Ptr msg) {
0215         auto mail = Mail::create(mResourceInstanceIdentifier);
0216         mail.setMimeMessage(msg->encodedContent(true));
0217         mail.setFolder(folder);
0218         VERIFYEXEC(Store::create(mail));
0219     };
0220 
0221     createMail(readMail("thread1_1"));
0222 
0223     VERIFYEXEC(ResourceControl::flushMessageQueue(QByteArrayList() << mResourceInstanceIdentifier));
0224 
0225     auto query = Sink::StandardQueries::threadLeaders(folder);
0226     query.resourceFilter(mResourceInstanceIdentifier);
0227     query.request<Mail::Subject>().request<Mail::MimeMessage>().request<Mail::Folder>().request<Mail::Date>();
0228 
0229     //Ensure we find the thread leader
0230     Mail threadLeader = [&] {
0231         auto mails = Store::read<Mail>(query);
0232         Q_ASSERT(mails.size() == 1);
0233         return mails.first();
0234     }();
0235 
0236     createMail(readMail("thread1_2"));
0237     createMail(readMail("thread1_3"));
0238     createMail(readMail("thread1_4"));
0239     createMail(readMail("thread1_5"));
0240     createMail(readMail("thread1_6"));
0241     createMail(readMail("thread1_7"));
0242     createMail(readMail("thread1_8")); //This mail is breaking the thread
0243     VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier));
0244 
0245     //Ensure the thread is complete
0246     {
0247         auto query = Sink::StandardQueries::completeThread(threadLeader);
0248         query.request<Mail::Subject>().request<Mail::MimeMessage>().request<Mail::Folder>().request<Mail::Date>();
0249 
0250         auto mails = Store::read<Mail>(query);
0251         QCOMPARE(mails.size(), 8);
0252     }
0253 
0254     {
0255         auto query = Sink::StandardQueries::threadLeaders(folder);
0256         Mail threadLeader2 = [&] {
0257             auto mails = Store::read<Mail>(query);
0258             Q_ASSERT(mails.size() == 1);
0259             return mails.first();
0260         }();
0261 
0262         {
0263             auto query = Sink::StandardQueries::completeThread(threadLeader2);
0264             query.request<Mail::Subject>().request<Mail::MimeMessage>().request<Mail::Folder>().request<Mail::Date>();
0265 
0266             auto mails = Store::read<Mail>(query);
0267             QCOMPARE(mails.size(), 8);
0268         }
0269     }
0270 }
0271 
0272 //Avoid accidentally merging or changing threads
0273 void MailThreadTest::testNoParentsWithModifications()
0274 {
0275     auto folder = Folder::create(mResourceInstanceIdentifier);
0276     folder.setName("folder2");
0277     VERIFYEXEC(Store::create(folder));
0278 
0279     auto createMail = [&] (const QString &subject) {
0280         auto message1 = KMime::Message::Ptr::create();
0281         message1->subject(true)->fromUnicodeString(subject, "utf8");
0282         message1->messageID(true)->fromUnicodeString("<" + subject + "@foobar.com" + ">", "utf8");
0283         message1->date(true)->setDateTime(QDateTime::currentDateTimeUtc());
0284         message1->assemble();
0285 
0286         auto mail = Mail::create(mResourceInstanceIdentifier);
0287         mail.setMimeMessage(message1->encodedContent(true));
0288         mail.setFolder(folder);
0289         return mail;
0290     };
0291 
0292     auto mail1 = createMail("1");
0293     VERIFYEXEC(Store::create(mail1));
0294     auto mail2 = createMail("2");
0295     VERIFYEXEC(Store::create(mail2));
0296     VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier));
0297 
0298     auto query = Sink::StandardQueries::threadLeaders(folder);
0299     query.resourceFilter(mResourceInstanceIdentifier);
0300     query.request<Mail::Subject>().request<Mail::MimeMessage>().request<Mail::Folder>().request<Mail::Date>().request<Mail::ThreadId>();
0301 
0302     QSet<QByteArray> threadIds;
0303     {
0304         auto mails = Store::read<Mail>(query);
0305         QCOMPARE(mails.size(), 2);
0306         for (const auto &m : mails) {
0307             threadIds << m.getProperty(Mail::ThreadId::name).toByteArray();
0308         }
0309     }
0310 
0311     auto readIndex = [&] (const QString &indexName, const QByteArray &lookupKey) {
0312         Index index(Sink::storageLocation(), mResourceInstanceIdentifier, indexName, Sink::Storage::DataStore::ReadOnly);
0313         QByteArrayList keys;
0314         index.lookup(lookupKey,
0315             [&](const QByteArray &value) { keys << QByteArray{value.constData(), value.size()}; return true; },
0316             [=](const Index::Error &error) { SinkWarning() << "Lookup error in secondary index: " << error.message; },
0317             false);
0318         return keys;
0319     };
0320     QCOMPARE(readIndex("mail.index.messageIdthreadId", "1@foobar.com").size(), 1);
0321     QCOMPARE(readIndex("mail.index.messageIdthreadId", "2@foobar.com").size(), 1);
0322 
0323     //We try to modify both mails on purpose
0324     auto checkMail = [&] (Mail mail1) {
0325         Mail modification = mail1;
0326         modification.setChangedProperties({});
0327         modification.setImportant(true);
0328         VERIFYEXEC(Store::modify(modification));
0329         VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier));
0330 
0331         QCOMPARE(readIndex("mail.index.messageIdthreadId", "1@foobar.com").size(), 1);
0332         QCOMPARE(readIndex("mail.index.messageIdthreadId", "2@foobar.com").size(), 1);
0333 
0334         {
0335             auto mails = Store::read<Mail>(query);
0336             QCOMPARE(mails.size(), 2);
0337             QSet<QByteArray> newThreadIds;
0338             for (const auto &m : mails) {
0339                 newThreadIds << m.getProperty(Mail::ThreadId::name).toByteArray();
0340             }
0341             QCOMPARE(threadIds, newThreadIds);
0342         }
0343     };
0344     checkMail(mail1);
0345     checkMail(mail2);
0346 }
0347 
0348 
0349 void MailThreadTest::testRealWorldThread2()
0350 {
0351     auto folder = Folder::create(mResourceInstanceIdentifier);
0352     folder.setName("folder2");
0353     VERIFYEXEC(Store::create(folder));
0354 
0355     auto createMail = [this, folder] (KMime::Message::Ptr msg) {
0356         auto mail = Mail::create(mResourceInstanceIdentifier);
0357         mail.setMimeMessage(msg->encodedContent(true));
0358         mail.setFolder(folder);
0359         VERIFYEXEC(Store::create(mail));
0360     };
0361 
0362     createMail(readMail(QString("thread2_%1").arg(1))); //30.10.18
0363     createMail(readMail(QString("thread2_%1").arg(2))); //02.11.18
0364     createMail(readMail(QString("thread2_%1").arg(3))); //07.11.18
0365     createMail(readMail(QString("thread2_%1").arg(4))); //09.11.18
0366     createMail(readMail(QString("thread2_%1").arg(14))); //13.11.18
0367     createMail(readMail(QString("thread2_%1").arg(12))); //16.11.18
0368     createMail(readMail(QString("thread2_%1").arg(6))); //16.11.18
0369     createMail(readMail(QString("thread2_%1").arg(9))); //23.11.18
0370     // createMail(readMail(QString("thread2_%1").arg(i))); //Different thread 18.1
0371     createMail(readMail(QString("thread2_%1").arg(7))); //04.12.18
0372     createMail(readMail(QString("thread2_%1").arg(17))); //18.12.18
0373     createMail(readMail(QString("thread2_%1").arg(13))); //22.1
0374     createMail(readMail(QString("thread2_%1").arg(15))); //25.1
0375     createMail(readMail(QString("thread2_%1").arg(11))); //28.1
0376     createMail(readMail(QString("thread2_%1").arg(10))); //29.1
0377     createMail(readMail(QString("thread2_%1").arg(16))); //29.1
0378 
0379 
0380     VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier));
0381 
0382     //Ensure we only got one thread
0383     const auto mails = Store::read<Mail>(Sink::StandardQueries::threadLeaders(folder));
0384     QCOMPARE(mails.size(), 1);
0385 
0386     //Ensure the thread is complete
0387     QCOMPARE(Store::read<Mail>(Sink::StandardQueries::completeThread(mails.first())).size(), 15);
0388 }
0389