/** short A fetching operation gets interrupted by switching to the offline mode */
void ImapMsgPartNetAccessManagerTest::testFetchResultOfflineSingle()
{
    COMMON_METADATA_CHAT_PLAIN_AND_SIGNED

    netAccessManager->setModelMessage(msg1);
    QNetworkRequest req;
    req.setUrl(QUrl(QLatin1String("trojita-imap://msg/0")));
    QNetworkReply *res = netAccessManager->get(req);
    QVERIFY(qobject_cast<Imap::Network::MsgPartNetworkReply*>(res));
    cClient(t.mk("UID FETCH 1 (BODY.PEEK[1])\r\n"));

    QPersistentModelIndex msg1p1 = msgListA.child(0, 0).child(0, 0);
    QVERIFY(msg1p1.isValid());
    QCOMPARE(msg1p1.data(Imap::Mailbox::RoleMessageUid), QVariant(1u));
    QCOMPARE(msg1p1.data(Imap::Mailbox::RoleIsFetched), QVariant(false));
    QCOMPARE(msg1p1.data(Imap::Mailbox::RoleIsUnavailable), QVariant(false));

    networkPolicy->setNetworkOffline();
    cClient(t.mk("LOGOUT\r\n"));
    cServer(t.last("OK logged out\r\n") + "* BYE eh\r\n");
    QCOMPARE(msg1p1.data(Imap::Mailbox::RoleIsFetched), QVariant(false));
    QCOMPARE(msg1p1.data(Imap::Mailbox::RoleIsUnavailable), QVariant(true));

#if QT_VERSION >= QT_VERSION_CHECK(4, 8, 0)
    QCOMPARE(res->isFinished(), true);
#endif
    QCOMPARE(res->error(), QNetworkReply::TimeoutError);
}
/** @short What happens when ENVELOPE doesn't arrive at the time that parts are already there? */
void CryptographyPGPTest::testDecryptWithoutEnvelope()
{
#ifdef TROJITA_HAVE_CRYPTO_MESSAGES
    model->setProperty("trojita-imap-delayed-fetch-part", 0);

    helperSyncBNoMessages();
    cServer("* 1 EXISTS\r\n");
    cClient(t.mk("UID FETCH 1:* (FLAGS)\r\n"));
    cServer("* 1 FETCH (UID 333 FLAGS ())\r\n" + t.last("OK fetched\r\n"));
    QCOMPARE(model->rowCount(msgListB), 1);
    QModelIndex msg = msgListB.child(0, 0);
    QVERIFY(msg.isValid());
    Cryptography::MessageModel msgModel(0, msg);
    msgModel.registerPartHandler(std::make_shared<Cryptography::GpgMeReplacer>());

    QCOMPARE(model->rowCount(msg), 0);
    cClient(t.mk("UID FETCH 333 (" FETCH_METADATA_ITEMS ")\r\n"));
    cServer("* 1 FETCH (UID 333 BODYSTRUCTURE " + bsEncrypted + ")\r\n" + t.last("OK fetched\r\n"));
    // notice that the ENVELOPE never arrived
    cEmpty();
    QVERIFY(model->rowCount(msg) > 0);
    QModelIndex mappedMsg = msgModel.index(0,0);
    QVERIFY(mappedMsg.isValid());
    QVERIFY(msgModel.rowCount(mappedMsg) > 0);

    QModelIndex data = mappedMsg.child(0, 0);
    QVERIFY(data.isValid());
    QCOMPARE(msgModel.rowCount(data), 0);
    QCOMPARE(data.data(Imap::Mailbox::RoleIsFetched).toBool(), false);

    cClientRegExp(t.mk("UID FETCH 333 \\((BODY\\.PEEK\\[2\\] BODY\\.PEEK\\[1\\]|BODY.PEEK\\[1\\] BODY\\.PEEK\\[2\\])\\)"));
    cServer("* 1 FETCH (UID 333 BODY[2] " + asLiteral(encValid) + " BODY[1] " + asLiteral("Version: 1\r\n") + ")\r\n"
            + t.last("OK fetched"));

    QSignalSpy qcaSuccessSpy(&msgModel, SIGNAL(rowsInserted(const QModelIndex &,int,int)));
    QSignalSpy qcaErrorSpy(&msgModel, SIGNAL(error(const QModelIndex &,QString,QString)));

    int i = 0;
    while (data.isValid() && data.data(Imap::Mailbox::RolePartCryptoNotFinishedYet).toBool() && i++ < 1000) {
        QTest::qWait(10);
    }
    // allow for event processing, so that the model can retrieve the results
    QCoreApplication::processEvents();

    QCOMPARE(data.data(Imap::Mailbox::RolePartCryptoNotFinishedYet).toBool(), false);
    QVERIFY(qcaSuccessSpy.isEmpty());
    QVERIFY(qcaErrorSpy.isEmpty());

    QVERIFY(!data.data(Imap::Mailbox::RoleIsFetched).toBool()); // because the ENVELOPE hasn't arrived yet

    cEmpty();
    QVERIFY(errorSpy->empty());
#else
    QSKIP("Cannot test without GpgME++ support");
#endif
}
void ImapMsgPartNetAccessManagerTest::testMessageParts()
{
    QFETCH(QByteArray, bodystructure);
    QFETCH(QByteArray, partId);
    QFETCH(QString, url);
    QFETCH(bool, validity);
    QFETCH(QByteArray, text);

    cServer("* 1 FETCH (UID 1 BODYSTRUCTURE (" + bodystructure + "))\r\n" +
            "* 2 FETCH (UID 2 BODYSTRUCTURE (" + bodystructure + "))\r\n"
            + t.last("OK fetched\r\n"));
    QVERIFY(model->rowCount(msg1) > 0);

    netAccessManager->setModelMessage(msg1);
    QNetworkRequest req;
    req.setUrl(QUrl(url));
    QNetworkReply *res = netAccessManager->get(req);
    if (validity) {
        QVERIFY(qobject_cast<Imap::Network::MsgPartNetworkReply*>(res));
        cClient(t.mk("UID FETCH 1 (BODY.PEEK[") + partId + "])\r\n");
        cServer("* 1 FETCH (UID 1 BODY[" + partId + "] {" + QByteArray::number(text.size()) + "}\r\n" +
                                                                               text + ")\r\n" + t.last("OK fetched\r\n"));
        cEmpty();
        QCOMPARE(text, res->readAll());
    } else {
        QVERIFY(qobject_cast<Imap::Network::ForbiddenReply*>(res));
    }
#if QT_VERSION >= QT_VERSION_CHECK(4, 8, 0)
    QCOMPARE(res->isFinished(), true);
#endif
    cEmpty();
    QVERIFY(errorSpy->isEmpty());
}
void ImapMsgPartNetAccessManagerTest::init()
{
    LibMailboxSync::init();

    // By default, there's a 50ms delay between the time we request a part download and the time it actually happens.
    // That's too long for a unit test.
    model->setProperty("trojita-imap-delayed-fetch-part", 0);

    networkPolicy = new Imap::Mailbox::DummyNetworkWatcher(0, model);
    netAccessManager = new Imap::Network::MsgPartNetAccessManager(0);

    initialMessages(2);
    QModelIndex m1 = msgListA.child(0, 0);
    QVERIFY(m1.isValid());
    QCOMPARE(model->rowCount(m1), 0);
    cClient(t.mk("UID FETCH 1:2 (" FETCH_METADATA_ITEMS ")\r\n"));
    //cServer()

    QCOMPARE(model->rowCount(msgListA), 2);
    msg1 = msgListA.child(0, 0);
    QVERIFY(msg1.isValid());
    msg2 = msgListA.child(1, 0);
    QVERIFY(msg2.isValid());

    cEmpty();
}
void ImapModelListChildMailboxesTest::testBackslashes()
{
    model->rowCount(QModelIndex());
    cClient(t.mk("LIST \"\" \"%\"\r\n"));
    cServer("* LIST (\\HasNoChildren) \"/\" Drafts\r\n"
            "* LIST (\\HasNoChildren) \"/\" {13}\r\nMail\\Papelera\r\n"
            "* LIST () \"/\" INBOX\r\n"
            + t.last("OK LIST completed\r\n"));
    QCOMPARE(model->rowCount(QModelIndex()), 4);
    QModelIndex withBackSlash = model->index(3, 0, QModelIndex());
    QVERIFY(withBackSlash.isValid());
    QCOMPARE(withBackSlash.data(Imap::Mailbox::RoleMailboxName).toString(), QString::fromUtf8("Mail\\Papelera"));
    QCOMPARE(withBackSlash.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 0);
    cClient(t.mk("STATUS \"Mail\\\\Papelera\" (MESSAGES UNSEEN RECENT)\r\n"));
    cServer("* STATUS {13}\r\nMail\\Papelera (MESSAGES 1 RECENT 1 UNSEEN 1)\r\n" + t.last("OK status done\r\n"));
    QCOMPARE(withBackSlash.data(Imap::Mailbox::RoleUnreadMessageCount).toInt(), 1);
    cEmpty();
}
void CryptographyPGPTest::testDecryption()
{
    QFETCH(QByteArray, bodystructure);
    QFETCH(QByteArray, cyphertext);
    QFETCH(QByteArray, plaintext);
    QFETCH(QString, from);
    QFETCH(bool, successful);

    // By default, there's a 50ms delay between the time we request a part download and the time it actually happens.
    // That's too long for a unit test.
    model->setProperty("trojita-imap-delayed-fetch-part", 0);

    helperSyncBNoMessages();
    cServer("* 1 EXISTS\r\n");
    cClient(t.mk("UID FETCH 1:* (FLAGS)\r\n"));
    cServer("* 1 FETCH (UID 333 FLAGS ())\r\n" + t.last("OK fetched\r\n"));

    QCOMPARE(model->rowCount(msgListB), 1);
    QModelIndex msg = msgListB.child(0, 0);
    QVERIFY(msg.isValid());
    QCOMPARE(model->rowCount(msg), 0);
    cClient(t.mk("UID FETCH 333 (" FETCH_METADATA_ITEMS ")\r\n"));
    cServer(helperCreateTrivialEnvelope(1, 333, QStringLiteral("subj"), from, bodystructure)
            + t.last("OK fetched\r\n"));
    cEmpty();
    QVERIFY(model->rowCount(msg) > 0);
    Cryptography::MessageModel msgModel(0, msg);
#ifdef TROJITA_HAVE_CRYPTO_MESSAGES
#  ifdef TROJITA_HAVE_GPGMEPP
    msgModel.registerPartHandler(std::make_shared<Cryptography::GpgMeReplacer>());
#  endif
#endif
    QModelIndex mappedMsg = msgModel.index(0,0);
    QVERIFY(mappedMsg.isValid());
    QVERIFY(msgModel.rowCount(mappedMsg) > 0);

    QModelIndex data = mappedMsg.child(0, 0);
    QVERIFY(data.isValid());
#ifdef TROJITA_HAVE_CRYPTO_MESSAGES
    QCOMPARE(msgModel.rowCount(data), 0);
    QCOMPARE(data.data(Imap::Mailbox::RoleIsFetched).toBool(), false);

    cClientRegExp(t.mk("UID FETCH 333 \\((BODY\\.PEEK\\[2\\] BODY\\.PEEK\\[1\\]|BODY.PEEK\\[1\\] BODY\\.PEEK\\[2\\])\\)"));
    cServer("* 1 FETCH (UID 333 BODY[2] " + asLiteral(cyphertext) + " BODY[1] " + asLiteral("Version: 1\r\n") + ")\r\n"
            + t.last("OK fetched"));

    QSignalSpy qcaSuccessSpy(&msgModel, SIGNAL(rowsInserted(const QModelIndex &,int,int)));
    QSignalSpy qcaErrorSpy(&msgModel, SIGNAL(error(const QModelIndex &,QString,QString)));

    int i = 0;
    while (data.isValid() && data.data(Imap::Mailbox::RolePartCryptoNotFinishedYet).toBool() && i++ < 1000) {
        QTest::qWait(10);
    }
    // allow for event processing, so that the model can retrieve the results
    QCoreApplication::processEvents();

    if (!qcaErrorSpy.isEmpty() && successful) {
        qDebug() << "Unexpected failure in crypto";
        for (int i = 0; i < qcaErrorSpy.size(); ++i) {
            qDebug() << qcaErrorSpy[i][1].toString();
            qDebug() << qcaErrorSpy[i][2].toString();
        }
    }

    if (successful) {
        QCOMPARE(qcaErrorSpy.empty(), successful);
        QCOMPARE(qcaSuccessSpy.empty(), !successful);
    }

    QVERIFY(data.data(Imap::Mailbox::RoleIsFetched).toBool());

    cEmpty();
    QVERIFY(errorSpy->empty());
#else
    QCOMPARE(msgModel.rowCount(data), 2);
    QCOMPARE(data.data(Imap::Mailbox::RoleIsFetched).toBool(), true);

    QCOMPARE(data.child(0, 0).data(Imap::Mailbox::RolePartMimeType).toString(), QLatin1String("application/pgp-encrypted"));
    QCOMPARE(data.child(1, 0).data(Imap::Mailbox::RolePartMimeType).toString(), QLatin1String("application/octet-stream"));
    cEmpty();

    QSKIP("Some tests were skipped because this build doesn't have GpgME++ support");
#endif
}
void CryptographyPGPTest::testVerification()
{
    QFETCH(QByteArray, signature);
    QFETCH(QByteArray, ptMimeHdr);
    QFETCH(QByteArray, plaintext);
    QFETCH(bool, successful);
    QFETCH(QString, from);
    QFETCH(QString, tldr);
    QFETCH(QString, longDesc);
    QFETCH(bool, validDisregardingTrust);
    QFETCH(bool, validCompletely);

    model->setProperty("trojita-imap-delayed-fetch-part", 0);
    helperSyncBNoMessages();
    cServer("* 1 EXISTS\r\n");
    cClient(t.mk("UID FETCH 1:* (FLAGS)\r\n"));
    cServer("* 1 FETCH (UID 333 FLAGS ())\r\n" + t.last("OK fetched\r\n"));
    QCOMPARE(model->rowCount(msgListB), 1);
    QModelIndex msg = msgListB.child(0, 0);
    QVERIFY(msg.isValid());
    QCOMPARE(model->rowCount(msg), 0);
    cClient(t.mk("UID FETCH 333 (" FETCH_METADATA_ITEMS ")\r\n"));
    cServer(helperCreateTrivialEnvelope(1, 333, QStringLiteral("subj"), from, QStringLiteral("("
            "(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" 423 14 NIL NIL NIL NIL)"
            "(\"application\" \"pgp-signature\" NIL NIL NIL \"7bit\" 851 NIL NIL NIL NIL)"
            " \"signed\" (\"boundary\" \"=-=-=\" \"micalg\" \"pgp-sha256\" \"protocol\" \"application/pgp-signature\")"
            " NIL NIL NIL)"))
            + t.last("OK fetched\r\n"));
    cEmpty();
    QVERIFY(model->rowCount(msg) > 0);
    Cryptography::MessageModel msgModel(0, msg);
#ifdef TROJITA_HAVE_CRYPTO_MESSAGES
#  ifdef TROJITA_HAVE_GPGMEPP
    msgModel.registerPartHandler(std::make_shared<Cryptography::GpgMeReplacer>());
#  endif
#endif
    QModelIndex mappedMsg = msgModel.index(0,0);
    QVERIFY(mappedMsg.isValid());
    QVERIFY(msgModel.rowCount(mappedMsg) > 0);

    QModelIndex data = mappedMsg.child(0, 0);
    QVERIFY(data.isValid());
#ifdef TROJITA_HAVE_CRYPTO_MESSAGES
    QCOMPARE(msgModel.rowCount(data), 0);
    QCOMPARE(data.data(Imap::Mailbox::RoleIsFetched).toBool(), false);

    cClientRegExp(t.mk("UID FETCH 333 \\((BODY\\.PEEK\\[(2|1|1\\.MIME)\\] ?){3}\\)"));
    cServer("* 1 FETCH (UID 333 BODY[2] " + asLiteral(signature) + " BODY[1] " + asLiteral(plaintext)
            + " BODY[1.MIME] " + asLiteral(ptMimeHdr) + ")\r\n"
            + t.last("OK fetched"));

    QSignalSpy qcaErrorSpy(&msgModel, SIGNAL(error(const QModelIndex &,QString,QString)));

    int i = 0;
    while (data.isValid() && data.data(Imap::Mailbox::RolePartCryptoNotFinishedYet).toBool() && qcaErrorSpy.empty() && i++ < 1000) {
        QTest::qWait(10);
    }
    // allow for event processing, so that the model can retrieve the results
    QCoreApplication::processEvents();

    if (!qcaErrorSpy.isEmpty() && successful) {
        qDebug() << "Unexpected failure in crypto";
        for (int i = 0; i < qcaErrorSpy.size(); ++i) {
            qDebug() << qcaErrorSpy[i][1].toString();
            qDebug() << qcaErrorSpy[i][2].toString();
        }
    }

    QCOMPARE(qcaErrorSpy.empty(), successful);
    QCOMPARE(data.data(Imap::Mailbox::RolePartCryptoTLDR).toString(), tldr);
    auto actualLongDesc = data.data(Imap::Mailbox::RolePartCryptoDetailedMessage).toString();
    if (!actualLongDesc.startsWith(longDesc)) {
        QCOMPARE(actualLongDesc, longDesc); // let's reuse this for debug output, and don't be scared about the misleading implications
    }

    QCOMPARE(data.data(Imap::Mailbox::RolePartSignatureVerifySupported).toBool(), successful);
    QCOMPARE(data.data(Imap::Mailbox::RolePartSignatureValidDisregardingTrust).toBool(), validDisregardingTrust);
    QCOMPARE(data.data(Imap::Mailbox::RolePartSignatureValidTrusted).toBool(), validCompletely);
    QCOMPARE(data.data(Imap::Mailbox::RolePartMimeType).toByteArray(), QByteArray("multipart/signed"));
    QVERIFY(data.data(Imap::Mailbox::RoleIsFetched).toBool() == successful);

    auto partIdx = data.child(0, 0);
    QCOMPARE(partIdx.data(Imap::Mailbox::RolePartMimeType).toByteArray(), QByteArray("text/plain"));
    QCOMPARE(partIdx.data(Imap::Mailbox::RolePartUnicodeText).toString(), QString::fromUtf8(plaintext));

    cEmpty();
    QVERIFY(errorSpy->empty());
#else
    QCOMPARE(msgModel.rowCount(data), 2);
    QCOMPARE(data.data(Imap::Mailbox::RoleIsFetched).toBool(), true);

    QCOMPARE(data.child(0, 0).data(Imap::Mailbox::RolePartMimeType).toString(), QLatin1String("application/pgp-encrypted"));
    QCOMPARE(data.child(1, 0).data(Imap::Mailbox::RolePartMimeType).toString(), QLatin1String("application/octet-stream"));
    cEmpty();

    QSKIP("Some tests were skipped because this build doesn't have GpgME++ support");
#endif
}