File indexing completed on 2024-11-24 04:44:31

0001 /*
0002    SPDX-FileCopyrightText: 2009 Thomas McGuire <mcguire@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "pop3test.h"
0008 
0009 #include <Akonadi/AgentInstanceCreateJob>
0010 #include <Akonadi/AgentManager>
0011 #include <Akonadi/CollectionFetchJob>
0012 #include <Akonadi/Control>
0013 #include <Akonadi/ItemDeleteJob>
0014 #include <Akonadi/ItemFetchJob>
0015 #include <Akonadi/ItemFetchScope>
0016 #include <Akonadi/Monitor>
0017 #include <Akonadi/ServerManager>
0018 #include <KMime/Message>
0019 #include <QElapsedTimer>
0020 #include <akonadi/qtest_akonadi.h>
0021 
0022 #include <QStandardPaths>
0023 
0024 QTEST_AKONADIMAIN(Pop3Test)
0025 
0026 using namespace Akonadi;
0027 
0028 constexpr int serverSettleTimeout = 200; /* ms */
0029 
0030 void Pop3Test::initTestCase()
0031 {
0032     AkonadiTest::checkTestIsIsolated();
0033     QVERIFY(Akonadi::Control::start());
0034 
0035     // switch all resources offline to reduce interference from them
0036     const auto instances{Akonadi::AgentManager::self()->instances()};
0037     for (Akonadi::AgentInstance agent : instances) {
0038         agent.setIsOnline(false);
0039     }
0040 
0041     /*
0042     qDebug() << "===========================================================";
0043     qDebug() << "============ Stopping for debugging =======================";
0044     qDebug() << "===========================================================";
0045     kill( qApp->applicationPid(), SIGSTOP );
0046     */
0047 
0048     //
0049     // Create the maildir and pop3 resources
0050     //
0051     AgentType maildirType = AgentManager::self()->type(QStringLiteral("akonadi_maildir_resource"));
0052     auto agentCreateJob = new AgentInstanceCreateJob(maildirType);
0053     const bool maildirCreateSuccess = agentCreateJob->exec();
0054     if (!maildirCreateSuccess) {
0055         qWarning() << "Failed to create maildir resource:" << agentCreateJob->errorString();
0056     }
0057     QVERIFY(maildirCreateSuccess);
0058     mMaildirIdentifier = agentCreateJob->instance().identifier();
0059 
0060     AgentType popType = AgentManager::self()->type(QStringLiteral("akonadi_pop3_resource"));
0061     agentCreateJob = new AgentInstanceCreateJob(popType);
0062     const bool pop3CreateSuccess = agentCreateJob->exec();
0063     if (!pop3CreateSuccess) {
0064         qWarning() << "Failed to create pop3 resource:" << agentCreateJob->errorString();
0065     }
0066     QVERIFY(pop3CreateSuccess);
0067     mPop3Identifier = agentCreateJob->instance().identifier();
0068 
0069     //
0070     // Configure the maildir resource
0071     //
0072     QString maildirRootPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + QLatin1StringView("tester");
0073     mMaildirPath = maildirRootPath + QLatin1StringView("/new");
0074     QDir::current().mkpath(mMaildirPath);
0075     QDir::current().mkpath(maildirRootPath + QLatin1StringView("/tmp"));
0076 
0077     QString service = QLatin1StringView("org.freedesktop.Akonadi.Resource.") + mMaildirIdentifier;
0078     if (Akonadi::ServerManager::hasInstanceIdentifier()) {
0079         service += QLatin1Char('.') + Akonadi::ServerManager::instanceIdentifier();
0080     }
0081 
0082     mMaildirSettingsInterface = new OrgKdeAkonadiMaildirSettingsInterface(service, QStringLiteral("/Settings"), QDBusConnection::sessionBus(), this);
0083     QDBusReply<void> setPathReply = mMaildirSettingsInterface->setPath(maildirRootPath);
0084     QVERIFY(setPathReply.isValid());
0085     mMaildirSettingsInterface->save();
0086     AgentManager::self()->instance(mMaildirIdentifier).reconfigure();
0087     QDBusReply<QString> getPathReply = mMaildirSettingsInterface->path();
0088     QCOMPARE(getPathReply.value(), maildirRootPath);
0089     AgentManager::self()->instance(mMaildirIdentifier).synchronize();
0090 
0091     //
0092     // Find the root maildir collection
0093     //
0094     bool found = false;
0095     QElapsedTimer time;
0096     time.start();
0097     while (!found) {
0098         auto job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive);
0099         QVERIFY(job->exec());
0100         const Collection::List collections = job->collections();
0101         for (const Collection &col : collections) {
0102             if (col.resource() == AgentManager::self()->instance(mMaildirIdentifier).identifier() && col.remoteId() == maildirRootPath) {
0103                 mMaildirCollection = col;
0104                 found = true;
0105                 break;
0106             }
0107         }
0108 
0109         QVERIFY(time.elapsed() < 10 * 1000); // maildir should not need more than 10 secs to sync
0110     }
0111 
0112     //
0113     // Start the fake POP3 server
0114     //
0115     mFakeServerThread = new FakeServerThread(this);
0116     mFakeServerThread->start();
0117     QTest::qWait(100);
0118     QVERIFY(mFakeServerThread->server() != nullptr);
0119 
0120     //
0121     // Configure the pop3 resource
0122     //
0123     mPOP3SettingsInterface = new OrgKdeAkonadiPOP3SettingsInterface(Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Resource, mPop3Identifier),
0124                                                                     QStringLiteral("/Settings"),
0125                                                                     QDBusConnection::sessionBus(),
0126                                                                     this);
0127 
0128     QDBusReply<uint> reply0 = mPOP3SettingsInterface->port();
0129     QVERIFY(reply0.isValid());
0130     QCOMPARE(reply0.value(), 110u);
0131 
0132     mPOP3SettingsInterface->setPort(5989).waitForFinished();
0133     mPOP3SettingsInterface->save();
0134     AgentManager::self()->instance(mPop3Identifier).reconfigure();
0135     QDBusReply<uint> reply = mPOP3SettingsInterface->port();
0136     QVERIFY(reply.isValid());
0137     QCOMPARE(reply.value(), 5989u);
0138 
0139     mPOP3SettingsInterface->setHost(QStringLiteral("localhost")).waitForFinished();
0140     mPOP3SettingsInterface->save();
0141     AgentManager::self()->instance(mPop3Identifier).reconfigure();
0142     QDBusReply<QString> reply2 = mPOP3SettingsInterface->host();
0143     QVERIFY(reply2.isValid());
0144     QCOMPARE(reply2.value(), QLatin1StringView("localhost"));
0145     mPOP3SettingsInterface->setLogin(QStringLiteral("HansWurst")).waitForFinished();
0146     mPOP3SettingsInterface->save();
0147     AgentManager::self()->instance(mPop3Identifier).reconfigure();
0148     QDBusReply<QString> reply3 = mPOP3SettingsInterface->login();
0149     QVERIFY(reply3.isValid());
0150     QCOMPARE(reply3.value(), QLatin1StringView("HansWurst"));
0151 
0152     mPOP3SettingsInterface->setUnitTestPassword(QStringLiteral("Geheim")).waitForFinished();
0153     mPOP3SettingsInterface->save();
0154     AgentManager::self()->instance(mPop3Identifier).reconfigure();
0155     QDBusReply<QString> reply4 = mPOP3SettingsInterface->unitTestPassword();
0156     QVERIFY(reply4.isValid());
0157     QCOMPARE(reply4.value(), QLatin1StringView("Geheim"));
0158 
0159     mPOP3SettingsInterface->setTargetCollection(mMaildirCollection.id()).waitForFinished();
0160     mPOP3SettingsInterface->save();
0161     AgentManager::self()->instance(mPop3Identifier).reconfigure();
0162     QDBusReply<qlonglong> reply5 = mPOP3SettingsInterface->targetCollection();
0163     QVERIFY(reply5.isValid());
0164     QCOMPARE(reply5.value(), mMaildirCollection.id());
0165 }
0166 
0167 void Pop3Test::cleanupTestCase()
0168 {
0169     // test might have failed before thread got created
0170     if (mFakeServerThread) {
0171         mFakeServerThread->quit();
0172         if (!mFakeServerThread->wait(10000)) {
0173             qWarning() << "The fake server thread has not yet finished, what is wrong!?";
0174         }
0175     }
0176 }
0177 
0178 static const QByteArray simpleMail1 =
0179     "From: \"Bill Lumbergh\" <BillLumbergh@initech.com>\r\n"
0180     "To: \"Peter Gibbons\" <PeterGibbons@initech.com>\r\n"
0181     "Subject: TPS Reports - New Cover Sheets\r\n"
0182     "MIME-Version: 1.0\r\n"
0183     "Content-Type: text/plain\r\n"
0184     "Date: Mon, 23 Mar 2009 18:04:05 +0300\r\n"
0185     "\r\n"
0186     "Hi, Peter. What's happening? We need to talk about your TPS reports.\r\n";
0187 
0188 static const QByteArray simpleMail2 =
0189     "From: \"Amy McCorkell\" <yooper@mtao.net>\r\n"
0190     "To: gov.palin@yaho.com\r\n"
0191     "Subject: HI SARAH\r\n"
0192     "MIME-Version: 1.0\r\n"
0193     "Content-Type: text/plain\r\n"
0194     "Date: Mon, 23 Mar 2009 18:04:05 +0300\r\n"
0195     "\r\n"
0196     "Hey Sarah,\r\n"
0197     "bla bla bla bla bla\r\n";
0198 
0199 static const QByteArray simpleMail3 =
0200     "From: chunkylover53@aol.com\r\n"
0201     "To: tylerdurden@paperstreetsoapcompany.com\r\n"
0202     "Subject: ILOVEYOU\r\n"
0203     "MIME-Version: 1.0\r\n"
0204     "Content-Type: text/plain\r\n"
0205     "Date: Mon, 23 Mar 2009 18:04:05 +0300\r\n"
0206     "\r\n"
0207     "kindly check the attached LOVELETTER coming from me.\r\n";
0208 
0209 static const QByteArray simpleMail4 =
0210     "From: karl@aol.com\r\n"
0211     "To: lenny@aol.com\r\n"
0212     "Subject: Who took the donuts?\r\n"
0213     "\r\n"
0214     "Hi Lenny, do you know who took all the donuts?\r\n";
0215 
0216 static const QByteArray simpleMail5 =
0217     "From: foo@bar.com\r\n"
0218     "To: bar@foo.com\r\n"
0219     "Subject: Hello\r\n"
0220     "\r\n"
0221     "Hello World!!\r\n";
0222 
0223 void Pop3Test::cleanupMaildir(const Akonadi::Item::List &items)
0224 {
0225     // Delete all mails so the maildir is clean for the next test
0226     if (!items.isEmpty()) {
0227         auto job = new ItemDeleteJob(items);
0228         QVERIFY(job->exec());
0229     }
0230 
0231     QElapsedTimer time;
0232     time.start();
0233     int lastCount = -1;
0234     for (;;) {
0235         QTest::qWait(500);
0236         QDir maildir(mMaildirPath);
0237         maildir.refresh();
0238         const int curCount = maildir.entryList(QDir::Files | QDir::NoDotAndDotDot).count();
0239 
0240         // Restart the timer when a mail arrives, as it shows that the maildir resource is
0241         // still alive and kicking.
0242         if (curCount != lastCount) {
0243             time.restart();
0244             lastCount = curCount;
0245         }
0246 
0247         if (curCount == 0) {
0248             break;
0249         }
0250 
0251         QVERIFY(time.elapsed() < 60000 || time.elapsed() > 80000000);
0252     }
0253 }
0254 
0255 void Pop3Test::checkMailsInMaildir(const QList<QByteArray> &mails)
0256 {
0257     // Now, test that all mails actually ended up in the maildir. Since the maildir resource
0258     // might be slower, give it a timeout so it can write the files to disk
0259     QElapsedTimer time;
0260     time.start();
0261     int lastCount = -1;
0262     for (;;) {
0263         QTest::qWait(500);
0264         QDir maildir(mMaildirPath);
0265         maildir.refresh();
0266         const int curCount = maildir.entryList(QDir::Files | QDir::NoDotAndDotDot).count();
0267 
0268         if (curCount == mails.count()) {
0269             break; // all done
0270         }
0271 
0272         // Restart the timer when a mail arrives, as it shows that the maildir resource is
0273         // still alive and kicking.
0274         if (curCount != lastCount) {
0275             time.start();
0276             lastCount = curCount;
0277         }
0278 
0279         QVERIFY(curCount <= mails.count());
0280         QVERIFY(time.elapsed() < 60000 || time.elapsed() > 80000000);
0281     }
0282 
0283     // TODO: check file contents as well or is this overkill?
0284 }
0285 
0286 Akonadi::Item::List Pop3Test::checkMailsOnAkonadiServer(const QList<QByteArray> &mails)
0287 {
0288     // The fake server got disconnected, which means the pop3 resource has entered the QUIT
0289     // stage. That means all messages should be on the server now, so test that.
0290     auto job = new ItemFetchJob(mMaildirCollection);
0291     job->fetchScope().fetchFullPayload();
0292     const bool ok = job->exec();
0293     Q_ASSERT(ok);
0294     if (!ok) {
0295         return {};
0296     }
0297     const Item::List items = job->items();
0298     Q_ASSERT(mails.size() == items.size());
0299 
0300     QSet<QByteArray> ourMailBodies;
0301     QSet<QByteArray> itemMailBodies;
0302 
0303     for (const Item &item : items) {
0304         auto itemMail = item.payload<KMime::Message::Ptr>();
0305         QByteArray itemMailBody = itemMail->body();
0306 
0307         // For some reason, the body in the maildir has one additional newline.
0308         // Get rid of this so we can compare them.
0309         // FIXME: is this a bug? Find out where the newline comes from!
0310         itemMailBody.chop(1);
0311         itemMailBodies.insert(itemMailBody);
0312     }
0313 
0314     for (const QByteArray &mail : mails) {
0315         KMime::Message::Ptr ourMail(new KMime::Message());
0316         ourMail->setContent(KMime::CRLFtoLF(mail));
0317         ourMail->parse();
0318         QByteArray ourMailBody = ourMail->body();
0319         ourMailBodies.insert(ourMailBody);
0320     }
0321 
0322     Q_ASSERT(ourMailBodies == itemMailBodies);
0323     return items;
0324 }
0325 
0326 void Pop3Test::syncAndWaitForFinish()
0327 {
0328     AgentManager::self()->instance(mPop3Identifier).synchronize();
0329 
0330     // The pop3 resource, ioslave and the fakeserver are all in different processes or threads.
0331     // We simply wait until the FakeServer got disconnected or until a timeout.
0332     // Since POP3 fetching can take longer, we reset the timeout timer when the FakeServer
0333     // does some processing.
0334     QElapsedTimer time;
0335     time.start();
0336     int lastProgress = -1;
0337     for (;;) {
0338         qApp->processEvents();
0339 
0340         // Finish correctly when the connection got closed
0341         if (mFakeServerThread->server()->gotDisconnected()) {
0342             break;
0343         }
0344 
0345         // Reset the timeout when the server is working
0346         const int newProgress = mFakeServerThread->server()->progress();
0347         if (newProgress != lastProgress) {
0348             time.restart();
0349             lastProgress = newProgress;
0350         }
0351 
0352         // Assert when nothing happens for a certain timeout, that indicates something went
0353         // wrong and is stuck somewhere
0354         if (time.elapsed() >= 60000) {
0355             Q_ASSERT_X(false, "poptest", "FakeServer timed out.");
0356             break;
0357         }
0358     }
0359 
0360     // Once the messages are processed give the Akonadi server and the maildir resource some time to
0361     // process the item operations. Do this by running a monitor together with a timer. Each captured
0362     // item operation bumps the timer to wait longer. After 200ms of inactivity the state is considered
0363     // stable and the test case can proceed.
0364     Akonadi::Monitor mon(this);
0365     mon.setResourceMonitored(mPop3Identifier.toLatin1());
0366     mon.setResourceMonitored(mMaildirIdentifier.toLatin1());
0367     QEventLoop settleLoop;
0368     QTimer settleTimer;
0369     settleTimer.setSingleShot(true);
0370     connect(&mon, &Akonadi::Monitor::itemAdded, this, [&](const Akonadi::Item &, const Akonadi::Collection &) {
0371         settleTimer.start(serverSettleTimeout);
0372     });
0373     connect(&mon, &Akonadi::Monitor::itemChanged, this, [&](const Akonadi::Item &, const QSet<QByteArray> &) {
0374         settleTimer.start(serverSettleTimeout);
0375     });
0376     connect(&mon, &Akonadi::Monitor::itemRemoved, this, [&](const Akonadi::Item &) {
0377         settleTimer.start(serverSettleTimeout);
0378     });
0379 
0380     settleTimer.start(serverSettleTimeout);
0381     connect(&settleTimer, &QTimer::timeout, this, [&]() {
0382         settleLoop.exit(0);
0383     });
0384     settleLoop.exec();
0385 }
0386 
0387 QString Pop3Test::loginSequence() const
0388 {
0389     return QStringLiteral(
0390         "C: USER HansWurst\r\n"
0391         "S: +OK May I have your password, please?\r\n"
0392         "C: PASS Geheim\r\n"
0393         "S: +OK Mailbox locked and ready\r\n");
0394 }
0395 
0396 QString Pop3Test::retrieveSequence(const QList<QByteArray> &mails, const QList<int> &exceptions) const
0397 {
0398     QString result;
0399     for (int i = 1; i <= mails.size(); i++) {
0400         if (!exceptions.contains(i)) {
0401             result += QLatin1StringView(
0402                 "C: RETR %RETR%\r\n"
0403                 "S: +OK Here is your spam\r\n"
0404                 "%MAIL%\r\n"
0405                 ".\r\n");
0406         }
0407     }
0408     return result;
0409 }
0410 
0411 QString Pop3Test::deleteSequence(int numToDelete) const
0412 {
0413     QString result;
0414     for (int i = 0; i < numToDelete; i++) {
0415         result += QLatin1StringView(
0416             "C: DELE %DELE%\r\n"
0417             "S: +OK message sent to /dev/null\r\n");
0418     }
0419     return result;
0420 }
0421 
0422 QString Pop3Test::quitSequence() const
0423 {
0424     return QStringLiteral(
0425         "C: QUIT\r\n"
0426         "S: +OK Have a nice day.\r\n");
0427 }
0428 
0429 QString Pop3Test::listSequence(const QList<QByteArray> &mails) const
0430 {
0431     QString result = QStringLiteral(
0432         "C: LIST\r\n"
0433         "S: +OK You got new spam\r\n");
0434     for (int i = 1; i <= mails.size(); i++) {
0435         result += QStringLiteral("%1 %MAILSIZE%\r\n").arg(i);
0436     }
0437     result += QLatin1StringView(".\r\n");
0438     return result;
0439 }
0440 
0441 QString Pop3Test::uidSequence(const QStringList &uids) const
0442 {
0443     QString result = QStringLiteral(
0444         "C: UIDL\r\n"
0445         "S: +OK\r\n");
0446     for (int i = 1; i <= uids.size(); i++) {
0447         result += QStringLiteral("%1 %2\r\n").arg(i).arg(uids[i - 1]);
0448     }
0449     result += QLatin1StringView(".\r\n");
0450     return result;
0451 }
0452 
0453 static bool sortedEqual(const QStringList &list1, const QStringList &list2)
0454 {
0455     QStringList sorted1 = list1;
0456     sorted1.sort();
0457     QStringList sorted2 = list2;
0458     sorted2.sort();
0459 
0460     return std::equal(sorted1.begin(), sorted1.end(), sorted2.begin());
0461 }
0462 
0463 void Pop3Test::lowerTimeOfSeenMail(const QString &uidOfMail, int secondsToLower)
0464 {
0465     const int index = mPOP3SettingsInterface->seenUidList().value().indexOf(uidOfMail);
0466     QList<int> seenTimeList = mPOP3SettingsInterface->seenUidTimeList().value();
0467     int msgTime = seenTimeList.at(index);
0468     msgTime -= secondsToLower;
0469     seenTimeList.replace(index, msgTime);
0470     mPOP3SettingsInterface->setSeenUidTimeList(seenTimeList).waitForFinished();
0471 }
0472 
0473 void Pop3Test::testSimpleDownload()
0474 {
0475     const QList<QByteArray> mails = {simpleMail1, simpleMail2, simpleMail3};
0476     const QStringList uids = {QStringLiteral("UID1"), QStringLiteral("UID2"), QStringLiteral("UID3")};
0477     mFakeServerThread->server()->setAllowedDeletions(QStringLiteral("1,2,3"));
0478     mFakeServerThread->server()->setAllowedRetrieves(QStringLiteral("1,2,3"));
0479     mFakeServerThread->server()->setMails(mails);
0480     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(mails) + uidSequence(uids) + retrieveSequence(mails)
0481                                                      + deleteSequence(mails.size()) + quitSequence());
0482 
0483     syncAndWaitForFinish();
0484     Akonadi::Item::List items = checkMailsOnAkonadiServer(mails);
0485     checkMailsInMaildir(mails);
0486     cleanupMaildir(items);
0487     mPOP3SettingsInterface->setSeenUidList(QStringList()).waitForFinished();
0488     mPOP3SettingsInterface->setSeenUidTimeList(QList<int>()).waitForFinished();
0489 }
0490 
0491 void Pop3Test::testBigFetch()
0492 {
0493     QList<QByteArray> mails;
0494     QStringList uids;
0495     QString allowedRetrs;
0496     mails.reserve(1000);
0497     uids.reserve(1000);
0498     for (int i = 0; i < 1000; i++) {
0499         QByteArray newMail = simpleMail1;
0500         newMail.append(QString::number(i + 1).toLatin1());
0501         mails << newMail;
0502         uids << QStringLiteral("UID%1").arg(i + 1);
0503         allowedRetrs += QString::number(i + 1) + QLatin1Char(',');
0504     }
0505     allowedRetrs.chop(1);
0506 
0507     mFakeServerThread->server()->setMails(mails);
0508     mFakeServerThread->server()->setAllowedRetrieves(allowedRetrs);
0509     mFakeServerThread->server()->setAllowedDeletions(allowedRetrs);
0510     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(mails) + uidSequence(uids) + retrieveSequence(mails)
0511                                                      + deleteSequence(mails.size()) + quitSequence());
0512 
0513     syncAndWaitForFinish();
0514     Akonadi::Item::List items = checkMailsOnAkonadiServer(mails);
0515     checkMailsInMaildir(mails);
0516     cleanupMaildir(items);
0517     mPOP3SettingsInterface->setSeenUidList(QStringList()).waitForFinished();
0518     mPOP3SettingsInterface->setSeenUidTimeList(QList<int>()).waitForFinished();
0519 }
0520 
0521 void Pop3Test::testSeenUIDCleanup()
0522 {
0523     //
0524     // First, fetch 3 normal mails, but leave them on the server.
0525     //
0526     mPOP3SettingsInterface->setLeaveOnServer(true).waitForFinished();
0527     const QList<QByteArray> mails = {simpleMail1, simpleMail2, simpleMail3};
0528     const QStringList uids = {QStringLiteral("UID1"), QStringLiteral("UID2"), QStringLiteral("UID3")};
0529     mFakeServerThread->server()->setAllowedDeletions(QString());
0530     mFakeServerThread->server()->setAllowedRetrieves(QStringLiteral("1,2,3"));
0531     mFakeServerThread->server()->setMails(mails);
0532     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(mails) + uidSequence(uids) + retrieveSequence(mails) + quitSequence());
0533 
0534     syncAndWaitForFinish();
0535     Akonadi::Item::List items = checkMailsOnAkonadiServer(mails);
0536     checkMailsInMaildir(mails);
0537     cleanupMaildir(items);
0538 
0539     QVERIFY(sortedEqual(uids, mPOP3SettingsInterface->seenUidList().value()));
0540     QVERIFY(mPOP3SettingsInterface->seenUidTimeList().value().size() == mPOP3SettingsInterface->seenUidList().value().size());
0541 
0542     //
0543     // Now, pretend that the messages were removed from the server in the meantime
0544     // by having no mails on the fake server.
0545     //
0546     mFakeServerThread->server()->setMails(QList<QByteArray>());
0547     mFakeServerThread->server()->setAllowedRetrieves(QString());
0548     mFakeServerThread->server()->setAllowedDeletions(QString());
0549     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(QList<QByteArray>()) + uidSequence(QStringList()) + quitSequence());
0550     syncAndWaitForFinish();
0551     items = checkMailsOnAkonadiServer(QList<QByteArray>());
0552     checkMailsInMaildir(QList<QByteArray>());
0553     cleanupMaildir(items);
0554 
0555     QVERIFY(mPOP3SettingsInterface->seenUidList().value().isEmpty());
0556     QVERIFY(mPOP3SettingsInterface->seenUidTimeList().value().size() == mPOP3SettingsInterface->seenUidList().value().size());
0557 
0558     mPOP3SettingsInterface->setLeaveOnServer(false).waitForFinished();
0559     mPOP3SettingsInterface->setSeenUidList(QStringList()).waitForFinished();
0560     mPOP3SettingsInterface->setSeenUidTimeList(QList<int>()).waitForFinished();
0561 }
0562 
0563 void Pop3Test::testSimpleLeaveOnServer()
0564 {
0565     mPOP3SettingsInterface->setLeaveOnServer(true).waitForFinished();
0566 
0567     const QList<QByteArray> mails = {simpleMail1, simpleMail2, simpleMail3};
0568     const QStringList uids = {QStringLiteral("UID1"), QStringLiteral("UID2"), QStringLiteral("UID3")};
0569     mFakeServerThread->server()->setMails(mails);
0570     mFakeServerThread->server()->setAllowedRetrieves(QStringLiteral("1,2,3"));
0571     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(mails) + uidSequence(uids) + retrieveSequence(mails) + quitSequence());
0572 
0573     syncAndWaitForFinish();
0574     Akonadi::Item::List items = checkMailsOnAkonadiServer(mails);
0575     checkMailsInMaildir(mails);
0576 
0577     // The resource should have saved the UIDs of the seen messages
0578     QVERIFY(sortedEqual(uids, mPOP3SettingsInterface->seenUidList().value()));
0579     QVERIFY(mPOP3SettingsInterface->seenUidTimeList().value().size() == mPOP3SettingsInterface->seenUidList().value().size());
0580     const auto seenUidTimeListValue{mPOP3SettingsInterface->seenUidTimeList().value()};
0581     for (int seenTime : seenUidTimeListValue) {
0582         // Those message were just downloaded from the fake server, so they are at maximum
0583         // 10 minutes old (for slooooow running tests)
0584         QVERIFY(seenTime >= time(nullptr) - 10 * 60);
0585     }
0586 
0587     //
0588     // OK, next mail check: We have to check that the old seen messages are not downloaded again,
0589     // only new mails.
0590     //
0591     QList<QByteArray> newMails(mails);
0592     newMails << simpleMail4;
0593     QStringList newUids(uids);
0594     newUids << QStringLiteral("newUID");
0595     const QList<int> idsToNotDownload = {1, 2, 3};
0596     mFakeServerThread->server()->setMails(newMails);
0597     mFakeServerThread->server()->setAllowedRetrieves(QStringLiteral("4"));
0598     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(newMails) + uidSequence(newUids)
0599                                                          + retrieveSequence(newMails, idsToNotDownload) + quitSequence(),
0600                                                      idsToNotDownload);
0601 
0602     syncAndWaitForFinish();
0603     items = checkMailsOnAkonadiServer(newMails);
0604     checkMailsInMaildir(newMails);
0605     QVERIFY(sortedEqual(newUids, mPOP3SettingsInterface->seenUidList().value()));
0606     QVERIFY(mPOP3SettingsInterface->seenUidTimeList().value().size() == mPOP3SettingsInterface->seenUidList().value().size());
0607 
0608     //
0609     // Ok, next test: When turning off leaving on the server, all mails should be deleted, but
0610     // none downloaded.
0611     //
0612     mPOP3SettingsInterface->setLeaveOnServer(false).waitForFinished();
0613 
0614     mFakeServerThread->server()->setAllowedDeletions(QStringLiteral("1,2,3,4"));
0615     mFakeServerThread->server()->setAllowedRetrieves(QString());
0616     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(newMails) + uidSequence(newUids) + deleteSequence(newMails.size())
0617                                                      + quitSequence());
0618 
0619     syncAndWaitForFinish();
0620     items = checkMailsOnAkonadiServer(newMails);
0621     checkMailsInMaildir(newMails);
0622     cleanupMaildir(items);
0623     QVERIFY(mPOP3SettingsInterface->seenUidList().value().isEmpty());
0624     QVERIFY(mPOP3SettingsInterface->seenUidTimeList().value().size() == mPOP3SettingsInterface->seenUidList().value().size());
0625     mPOP3SettingsInterface->setSeenUidList(QStringList()).waitForFinished();
0626     mPOP3SettingsInterface->setSeenUidTimeList(QList<int>()).waitForFinished();
0627 }
0628 
0629 void Pop3Test::testTimeBasedLeaveRule()
0630 {
0631     mPOP3SettingsInterface->setLeaveOnServer(true).waitForFinished();
0632     mPOP3SettingsInterface->setLeaveOnServerDays(2).waitForFinished();
0633 
0634     //
0635     // First download 3 mails and leave them on the server
0636     //
0637     const QList<QByteArray> mails = {simpleMail1, simpleMail2, simpleMail3};
0638     QStringList uids = {QStringLiteral("UID1"), QStringLiteral("UID2"), QStringLiteral("UID3")};
0639     mFakeServerThread->server()->setMails(mails);
0640     mFakeServerThread->server()->setAllowedRetrieves(QStringLiteral("1,2,3"));
0641     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(mails) + uidSequence(uids) + retrieveSequence(mails) + quitSequence());
0642 
0643     syncAndWaitForFinish();
0644     Akonadi::Item::List items = checkMailsOnAkonadiServer(mails);
0645     checkMailsInMaildir(mails);
0646 
0647     QVERIFY(sortedEqual(uids, mPOP3SettingsInterface->seenUidList().value()));
0648     QVERIFY(mPOP3SettingsInterface->seenUidTimeList().value().size() == mPOP3SettingsInterface->seenUidList().value().size());
0649 
0650     //
0651     // Now, modify the seenUidTimeList on the server for UID2 to pretend it
0652     // was downloaded 3 days ago, which means it should be deleted.
0653     //
0654     lowerTimeOfSeenMail(QStringLiteral("UID2"), 60 * 60 * 24 * 3);
0655 
0656     const QList<int> idsToNotDownload = {1, 2, 3};
0657     mFakeServerThread->server()->setAllowedDeletions(QStringLiteral("2"));
0658     mFakeServerThread->server()->setAllowedRetrieves(QString());
0659     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(mails) + uidSequence(uids) + deleteSequence(1) + quitSequence(),
0660                                                      idsToNotDownload);
0661     syncAndWaitForFinish();
0662     items = checkMailsOnAkonadiServer(mails);
0663     checkMailsInMaildir(mails);
0664     cleanupMaildir(items);
0665 
0666     uids.removeAll(QStringLiteral("UID2"));
0667     QVERIFY(sortedEqual(uids, mPOP3SettingsInterface->seenUidList().value()));
0668     QVERIFY(mPOP3SettingsInterface->seenUidTimeList().value().size() == mPOP3SettingsInterface->seenUidList().value().size());
0669     const auto seenUidTimeListValue{mPOP3SettingsInterface->seenUidTimeList().value()};
0670     for (int seenTime : seenUidTimeListValue) {
0671         QVERIFY(seenTime >= time(nullptr) - 10 * 60);
0672     }
0673 
0674     mPOP3SettingsInterface->setLeaveOnServer(false).waitForFinished();
0675     mPOP3SettingsInterface->setLeaveOnServerDays(0).waitForFinished();
0676     mPOP3SettingsInterface->setSeenUidTimeList(QList<int>()).waitForFinished();
0677     mPOP3SettingsInterface->setSeenUidList(QStringList()).waitForFinished();
0678 }
0679 
0680 void Pop3Test::testCountBasedLeaveRule()
0681 {
0682     mPOP3SettingsInterface->setLeaveOnServer(true).waitForFinished();
0683     mPOP3SettingsInterface->setLeaveOnServerCount(3).waitForFinished();
0684 
0685     //
0686     // First download 3 mails and leave them on the server
0687     //
0688     const QList<QByteArray> mails = {simpleMail1, simpleMail2, simpleMail3};
0689     const QStringList uids = {QStringLiteral("UID1"), QStringLiteral("UID2"), QStringLiteral("UID3")};
0690     mFakeServerThread->server()->setMails(mails);
0691     mFakeServerThread->server()->setAllowedRetrieves(QStringLiteral("1,2,3"));
0692     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(mails) + uidSequence(uids) + retrieveSequence(mails) + quitSequence());
0693 
0694     syncAndWaitForFinish();
0695     checkMailsOnAkonadiServer(mails);
0696     checkMailsInMaildir(mails);
0697 
0698     // Make the 3 just downloaded mails appear older than they are
0699     lowerTimeOfSeenMail(QStringLiteral("UID1"), 60 * 60 * 24 * 2);
0700     lowerTimeOfSeenMail(QStringLiteral("UID2"), 60 * 60 * 24 * 1);
0701     lowerTimeOfSeenMail(QStringLiteral("UID3"), 60 * 60 * 24 * 3);
0702 
0703     //
0704     // Now, download 2 more mails. Since only 3 mails are allowed to be left
0705     // on the server, the oldest ones, UID1 and UID3, should be deleted
0706     //
0707     const QList<QByteArray> moreMails = {simpleMail4, simpleMail5};
0708     const QStringList moreUids = {QStringLiteral("UID4"), QStringLiteral("UID5")};
0709     mFakeServerThread->server()->setMails(mails + moreMails);
0710     mFakeServerThread->server()->setAllowedRetrieves(QStringLiteral("4,5"));
0711     mFakeServerThread->server()->setAllowedDeletions(QStringLiteral("1,3"));
0712     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(mails + moreMails) + uidSequence(uids + moreUids)
0713                                                          + retrieveSequence(moreMails) + deleteSequence(2) + quitSequence(),
0714                                                      {1, 2, 3});
0715 
0716     syncAndWaitForFinish();
0717     Akonadi::Item::List items = checkMailsOnAkonadiServer(mails + moreMails);
0718     checkMailsInMaildir(mails + moreMails);
0719     cleanupMaildir(items);
0720 
0721     const QStringList uidsLeft = {QStringLiteral("UID2"), QStringLiteral("UID4"), QStringLiteral("UID5")};
0722     QVERIFY(sortedEqual(uidsLeft, mPOP3SettingsInterface->seenUidList().value()));
0723     QVERIFY(mPOP3SettingsInterface->seenUidTimeList().value().size() == mPOP3SettingsInterface->seenUidList().value().size());
0724 
0725     mPOP3SettingsInterface->setLeaveOnServer(false).waitForFinished();
0726     mPOP3SettingsInterface->setLeaveOnServerCount(0).waitForFinished();
0727     mPOP3SettingsInterface->setSeenUidTimeList(QList<int>()).waitForFinished();
0728     mPOP3SettingsInterface->setSeenUidList(QStringList()).waitForFinished();
0729 }
0730 
0731 void Pop3Test::testSizeBasedLeaveRule()
0732 {
0733     mPOP3SettingsInterface->setLeaveOnServer(true).waitForFinished();
0734     mPOP3SettingsInterface->setLeaveOnServerSize(10).waitForFinished(); // 10 MB
0735 
0736     //
0737     // First download 3 mails and leave them on the server.
0738     //
0739     const QList<QByteArray> mails = {simpleMail1, simpleMail2, simpleMail3};
0740     const QStringList uids = {QStringLiteral("UID1"), QStringLiteral("UID2"), QStringLiteral("UID3")};
0741     mFakeServerThread->server()->setMails(mails);
0742     mFakeServerThread->server()->setAllowedRetrieves(QStringLiteral("1,2,3"));
0743     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(mails) + uidSequence(uids) + retrieveSequence(mails) + quitSequence());
0744 
0745     syncAndWaitForFinish();
0746     checkMailsOnAkonadiServer(mails);
0747     checkMailsInMaildir(mails);
0748 
0749     // Make the 3 just downloaded mails appear older than they are
0750     lowerTimeOfSeenMail(QStringLiteral("UID1"), 60 * 60 * 24 * 2);
0751     lowerTimeOfSeenMail(QStringLiteral("UID2"), 60 * 60 * 24 * 1);
0752     lowerTimeOfSeenMail(QStringLiteral("UID3"), 60 * 60 * 24 * 3);
0753 
0754     // Now, do another mail check, but with no new mails on the server.
0755     // Instead we let the server pretend that the mails have a fake size,
0756     // each 7 MB. That means the two oldest get deleted, because the total
0757     // mail size is over 10 MB with them.
0758     mFakeServerThread->server()->setMails(mails);
0759     mFakeServerThread->server()->setAllowedRetrieves(QString());
0760     mFakeServerThread->server()->setAllowedDeletions(QStringLiteral("1,3"));
0761     mFakeServerThread->server()->setNextConversation(loginSequence()
0762                                                      + QLatin1StringView("C: LIST\r\n"
0763                                                                          "S: +OK You got new spam\r\n"
0764                                                                          "1 7340032\r\n"
0765                                                                          "2 7340032\r\n"
0766                                                                          "3 7340032\r\n"
0767                                                                          ".\r\n")
0768                                                      + uidSequence(uids) + deleteSequence(2) + quitSequence());
0769 
0770     syncAndWaitForFinish();
0771     Akonadi::Item::List items = checkMailsOnAkonadiServer(mails);
0772     checkMailsInMaildir(mails);
0773     cleanupMaildir(items);
0774 
0775     const QStringList uidsLeft = {QStringLiteral("UID2")};
0776     QVERIFY(sortedEqual(uidsLeft, mPOP3SettingsInterface->seenUidList().value()));
0777     QVERIFY(mPOP3SettingsInterface->seenUidTimeList().value().size() == mPOP3SettingsInterface->seenUidList().value().size());
0778 
0779     mPOP3SettingsInterface->setLeaveOnServer(false).waitForFinished();
0780     mPOP3SettingsInterface->setLeaveOnServerCount(0).waitForFinished();
0781     mPOP3SettingsInterface->setLeaveOnServerSize(0).waitForFinished();
0782     mPOP3SettingsInterface->setSeenUidTimeList(QList<int>()).waitForFinished();
0783     mPOP3SettingsInterface->setSeenUidList(QStringList()).waitForFinished();
0784 }
0785 
0786 void Pop3Test::testMixedLeaveRules()
0787 {
0788     mPOP3SettingsInterface->setLeaveOnServer(true).waitForFinished();
0789     //
0790     // Generate 10 mails
0791     //
0792     QList<QByteArray> mails;
0793     mails.reserve(10);
0794     QStringList uids;
0795     uids.reserve(10);
0796     QString allowedRetrs;
0797     for (int i = 0; i < 10; i++) {
0798         QByteArray newMail = simpleMail1;
0799         newMail.append(QString::number(i + 1).toLatin1());
0800         mails << newMail;
0801         uids << QStringLiteral("UID%1").arg(i + 1);
0802         allowedRetrs += QString::number(i + 1) + QLatin1Char(',');
0803     }
0804     allowedRetrs.chop(1);
0805 
0806     //
0807     // Now, download these 10 mails
0808     //
0809     mFakeServerThread->server()->setMails(mails);
0810     mFakeServerThread->server()->setAllowedRetrieves(allowedRetrs);
0811     mFakeServerThread->server()->setNextConversation(loginSequence() + listSequence(mails) + uidSequence(uids) + retrieveSequence(mails) + quitSequence());
0812 
0813     syncAndWaitForFinish();
0814     checkMailsOnAkonadiServer(mails);
0815     checkMailsInMaildir(mails);
0816 
0817     // Fake the time of the messages, UID1 is one day old, UID2 is two days old, etc
0818     for (int i = 1; i <= 10; i++) {
0819         lowerTimeOfSeenMail(QStringLiteral("UID%1").arg(i), 60 * 60 * 24 * i);
0820     }
0821 
0822     mPOP3SettingsInterface->setLeaveOnServer(true).waitForFinished();
0823     mPOP3SettingsInterface->setLeaveOnServerSize(25).waitForFinished(); // UID 4, 5 oldest here
0824     mPOP3SettingsInterface->setLeaveOnServerCount(5).waitForFinished(); // UID 6, 7 oldest here
0825     mPOP3SettingsInterface->setLeaveOnServerDays(7).waitForFinished(); // UID 8, 9 and 10 too old
0826 
0827     // Ok, now we do another mail check that only deletes stuff from the server.
0828     // Above are the UIDs that should be deleted.
0829     mFakeServerThread->server()->setMails(mails);
0830     mFakeServerThread->server()->setAllowedRetrieves(QString());
0831     mFakeServerThread->server()->setAllowedDeletions(QStringLiteral("4,5,6,7,8,9,10"));
0832     mFakeServerThread->server()->setNextConversation(loginSequence()
0833                                                      + QLatin1StringView("C: LIST\r\n"
0834                                                                          "S: +OK You got new spam\r\n"
0835                                                                          "1 7340032\r\n"
0836                                                                          "2 7340032\r\n"
0837                                                                          "3 7340032\r\n"
0838                                                                          "4 7340032\r\n"
0839                                                                          "5 7340032\r\n"
0840                                                                          "6 7340032\r\n"
0841                                                                          "7 7340032\r\n"
0842                                                                          "8 7340032\r\n"
0843                                                                          "9 7340032\r\n"
0844                                                                          "10 7340032\r\n"
0845                                                                          ".\r\n")
0846                                                      + uidSequence(uids) + deleteSequence(7) + quitSequence());
0847 
0848     syncAndWaitForFinish();
0849     Akonadi::Item::List items = checkMailsOnAkonadiServer(mails);
0850     checkMailsInMaildir(mails);
0851     cleanupMaildir(items);
0852 
0853     const QStringList uidsLeft = {QStringLiteral("UID1"), QStringLiteral("UID2"), QStringLiteral("UID3")};
0854     QVERIFY(sortedEqual(uidsLeft, mPOP3SettingsInterface->seenUidList().value()));
0855     QVERIFY(mPOP3SettingsInterface->seenUidTimeList().value().size() == mPOP3SettingsInterface->seenUidList().value().size());
0856 
0857     mPOP3SettingsInterface->setLeaveOnServer(false).waitForFinished();
0858     mPOP3SettingsInterface->setLeaveOnServerCount(0).waitForFinished();
0859     mPOP3SettingsInterface->setLeaveOnServerSize(0).waitForFinished();
0860     mPOP3SettingsInterface->setSeenUidTimeList(QList<int>()).waitForFinished();
0861     mPOP3SettingsInterface->setSeenUidList(QStringList()).waitForFinished();
0862 }
0863 
0864 #include "moc_pop3test.cpp"