/*
    This file is part of libkleopatra's test suite.
    SPDX-FileCopyrightText: 2021 g10 Code GmbH
    SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "testhelpers.h"

#include <Libkleo/Compliance>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/KeySelectionCombo>
#include <Libkleo/NewKeyApprovalDialog>
#include <Libkleo/Predicates>
#include <Libkleo/Test>

#include <QCheckBox>
#include <QGroupBox>
#include <QLabel>
#include <QObject>
#include <QPushButton>
#include <QRadioButton>
#include <QSignalSpy>
#include <QTest>

#include <gpgme++/key.h>

#include <gpgme.h>

#include <memory>
#include <set>

using namespace Kleo;
using namespace Qt::StringLiterals;

namespace QTest
{
template<>
inline char *toString(const bool &t)
{
    return t ? qstrdup("true") : qstrdup("false");
}

template<>
inline bool qCompare(bool const &t1, bool const &t2, const char *actual, const char *expected, const char *file, int line)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
    auto formatter = Internal::genericToString<bool>;
    return compare_helper(t1 == t2, "Compared values are not the same", &t1, &t2, formatter, formatter, actual, expected, file, line);
#else
    auto actualVal = [&t1] {
        return toString(t1);
    };
    auto expectedVal = [&t2] {
        return toString(t2);
    };
    return compare_helper(t1 == t2, "Compared values are not the same", actualVal, expectedVal, actual, expected, file, line);
#endif
}

template<>
inline char *toString(const GpgME::Protocol &t)
{
    return qstrdup(Formatting::displayName(t).toLocal8Bit().constData());
}

template<>
inline bool qCompare(GpgME::Protocol const &t1, GpgME::Protocol const &t2, const char *actual, const char *expected, const char *file, int line)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
    auto formatter = Internal::genericToString<GpgME::Protocol>;
    return compare_helper(t1 == t2, "Compared values are not the same", &t1, &t2, formatter, formatter, actual, expected, file, line);
#else
    auto actualVal = [&t1] {
        return toString(t1);
    };
    auto expectedVal = [&t2] {
        return toString(t2);
    };
    return compare_helper(t1 == t2, "Compared values are not the same", actualVal, expectedVal, actual, expected, file, line);
#endif
}
}

namespace
{

// copied from NewKeyApprovalDialog::Private
enum Action {
    Unset,
    GenerateKey,
    IgnoreKey,
};

auto mapValidity(GpgME::UserID::Validity validity)
{
    switch (validity) {
    default:
    case GpgME::UserID::Unknown:
        return GPGME_VALIDITY_UNKNOWN;
    case GpgME::UserID::Undefined:
        return GPGME_VALIDITY_UNDEFINED;
    case GpgME::UserID::Never:
        return GPGME_VALIDITY_NEVER;
    case GpgME::UserID::Marginal:
        return GPGME_VALIDITY_MARGINAL;
    case GpgME::UserID::Full:
        return GPGME_VALIDITY_FULL;
    case GpgME::UserID::Ultimate:
        return GPGME_VALIDITY_ULTIMATE;
    }
}

// copied from gpgme; slightly modified
void _gpgme_key_add_subkey(gpgme_key_t key, gpgme_subkey_t *r_subkey)
{
    gpgme_subkey_t subkey;

    subkey = static_cast<gpgme_subkey_t>(calloc(1, sizeof *subkey));
    Q_ASSERT(subkey);
    subkey->keyid = subkey->_keyid;
    subkey->_keyid[16] = '\0';

    if (!key->subkeys) {
        key->subkeys = subkey;
    }
    if (key->_last_subkey) {
        key->_last_subkey->next = subkey;
    }
    key->_last_subkey = subkey;

    *r_subkey = subkey;
}

GpgME::Key createTestKey(const char *uid,
                         GpgME::Protocol protocol = GpgME::UnknownProtocol,
                         KeyCache::KeyUsage usage = KeyCache::KeyUsage::AnyUsage,
                         GpgME::UserID::Validity validity = GpgME::UserID::Full)
{
    static int count = 0;
    count++;

    gpgme_key_t key;
    gpgme_key_from_uid(&key, uid);
    Q_ASSERT(key);
    Q_ASSERT(key->uids);
    if (protocol != GpgME::UnknownProtocol) {
        key->protocol = protocol == GpgME::OpenPGP ? GPGME_PROTOCOL_OpenPGP : GPGME_PROTOCOL_CMS;
    }
    const QByteArray fingerprint = QByteArray::number(count, 16).rightJustified(40, '0');
    key->fpr = strdup(fingerprint.constData());
    key->revoked = 0;
    key->expired = 0;
    key->disabled = 0;
    key->can_encrypt = int(usage == KeyCache::KeyUsage::AnyUsage || usage == KeyCache::KeyUsage::Encrypt);
    key->can_sign = int(usage == KeyCache::KeyUsage::AnyUsage || usage == KeyCache::KeyUsage::Sign);
    key->has_encrypt = int(usage == KeyCache::KeyUsage::AnyUsage || usage == KeyCache::KeyUsage::Encrypt);
    key->has_sign = int(usage == KeyCache::KeyUsage::AnyUsage || usage == KeyCache::KeyUsage::Sign);
    key->secret = 1;
    key->uids->validity = mapValidity(validity);
    key->keylist_mode = GPGME_KEYLIST_MODE_VALIDATE;

    // add a usable VS-NfD-compliant subkey
    gpgme_subkey_t subkey;
    _gpgme_key_add_subkey(key, &subkey);
    subkey->is_de_vs = 1;
    subkey->can_encrypt = key->can_encrypt;
    subkey->can_sign = key->can_sign;

    return GpgME::Key(key, false);
}

auto testKey(const char *address, GpgME::Protocol protocol = GpgME::UnknownProtocol)
{
    const auto email = GpgME::UserID::addrSpecFromString(address);
    const auto keys = KeyCache::instance()->findByEMailAddress(email);
    for (const auto &key : keys) {
        if (protocol == GpgME::UnknownProtocol || key.protocol() == protocol) {
            return key;
        }
    }
    return GpgME::Key();
}

#define waitForKeySelectionCombosBeingInitialized(dialog)                                                                                                      \
    do {                                                                                                                                                       \
        QVERIFY(dialog);                                                                                                                                       \
        auto combo = dialog->findChild<KeySelectionCombo *>();                                                                                                 \
        QVERIFY(combo);                                                                                                                                        \
        const auto spy = std::make_unique<QSignalSpy>(combo, &KeySelectionCombo::keyListingFinished);                                                          \
        QVERIFY(spy->isValid());                                                                                                                               \
        QVERIFY(spy->wait(10));                                                                                                                                \
    } while (false)

template<typename T>
struct Widgets {
    std::vector<T *> visible;
    std::vector<T *> hidden;
};

template<typename T>
Widgets<T> visibleAndHiddenWidgets(const QList<T *> &widgets)
{
    Widgets<T> result;
    std::partition_copy(std::begin(widgets),
                        std::end(widgets),
                        std::back_inserter(result.visible),
                        std::back_inserter(result.hidden),
                        std::mem_fn(&QWidget::isVisible));
    return result;
}

enum Visibility {
    IsHidden,
    IsVisible,
};

enum CheckedState {
    IsUnchecked,
    IsChecked,
};

#define verifyProtocolButton(button, expectedVisibility, expectedCheckedState)                                                                                 \
    do {                                                                                                                                                       \
        QVERIFY(button);                                                                                                                                       \
        QCOMPARE(button->isVisible(), expectedVisibility == IsVisible);                                                                                        \
        QCOMPARE(button->isChecked(), expectedCheckedState == IsChecked);                                                                                      \
    } while (false)

#define verifyWidgetVisibility(widget, expectedVisibility)                                                                                                     \
    do {                                                                                                                                                       \
        QVERIFY(widget);                                                                                                                                       \
        QCOMPARE(widget->isVisible(), expectedVisibility == IsVisible);                                                                                        \
    } while (false)

#define verifyWidgetsVisibility(widgets, expectedVisibility)                                                                                                   \
    do {                                                                                                                                                       \
        for (auto w : widgets) {                                                                                                                               \
            verifyWidgetVisibility(w, expectedVisibility);                                                                                                     \
        }                                                                                                                                                      \
    } while (false)

#define verifyProtocolLabels(labels, expectedNumber, expectedVisibility)                                                                                       \
    do {                                                                                                                                                       \
        QCOMPARE(labels.size(), expectedNumber);                                                                                                               \
        verifyWidgetsVisibility(labels, expectedVisibility);                                                                                                   \
    } while (false)

static bool listsOfKeysAreEqual(const std::vector<GpgME::Key> &l1, const std::vector<GpgME::Key> &l2)
{
    return std::equal(std::begin(l1), std::end(l1), std::begin(l2), std::end(l2), ByFingerprint<std::equal_to>());
}

#define verifySolution(actual, expected)                                                                                                                       \
    do {                                                                                                                                                       \
        QCOMPARE(actual.protocol, expected.protocol);                                                                                                          \
        QVERIFY(listsOfKeysAreEqual(actual.signingKeys, expected.signingKeys));                                                                                \
        QVERIFY(std::equal(actual.encryptionKeys.constKeyValueBegin(),                                                                                         \
                           actual.encryptionKeys.constKeyValueEnd(),                                                                                           \
                           expected.encryptionKeys.constKeyValueBegin(),                                                                                       \
                           expected.encryptionKeys.constKeyValueEnd(),                                                                                         \
                           [](const auto &kv1, const auto &kv2) {                                                                                              \
                               return kv1.first == kv2.first && listsOfKeysAreEqual(kv1.second, kv2.second);                                                   \
                           }));                                                                                                                                \
    } while (false)

void switchKeySelectionCombosFromGenerateKeyToIgnoreKey(const QList<KeySelectionCombo *> &combos)
{
    for (auto combo : combos) {
        if (combo->currentData(Qt::UserRole).toInt() == GenerateKey) {
            const auto ignoreIndex = combo->findData(IgnoreKey);
            QVERIFY(ignoreIndex != -1);
            combo->setCurrentIndex(ignoreIndex);
        }
    }
}

}

class NewKeyApprovalDialogTest : public QObject
{
    Q_OBJECT

private Q_SLOTS:
    void initTestCase()
    {
#ifdef Q_OS_WIN
        QSKIP("UI tests don't run on Windows");
#endif
    }

    void init()
    {
        // hold a reference to the key cache to avoid rebuilding while the test is running
        mKeyCache = KeyCache::instance();

        KeyCache::mutableInstance()->setKeys({
            createTestKey("sender@example.net", GpgME::OpenPGP, KeyCache::KeyCache::KeyUsage::AnyUsage),
            createTestKey("sender@example.net", GpgME::CMS, KeyCache::KeyCache::KeyUsage::AnyUsage),
            createTestKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP, KeyCache::KeyCache::KeyUsage::Encrypt),
            createTestKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS, KeyCache::KeyCache::KeyUsage::Encrypt),
            createTestKey("Marginal Validity <marginal-openpgp@example.net>", GpgME::OpenPGP, KeyCache::KeyCache::KeyUsage::Encrypt, GpgME::UserID::Marginal),
        });
    }

    void cleanup()
    {
        // verify that nobody else holds a reference to the key cache
        QCOMPARE(mKeyCache.use_count(), 1);
        mKeyCache.reset();
    }

    void test__verify_test_keys()
    {
        QVERIFY(!testKey("sender@example.net", GpgME::OpenPGP).isNull());
        QVERIFY(!testKey("sender@example.net", GpgME::CMS).isNull());
        QVERIFY(!testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP).isNull());
        QVERIFY(!testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS).isNull());
        QVERIFY(!testKey("Marginal Validity <marginal-openpgp@example.net>", GpgME::OpenPGP).isNull());
    }

    void test__both_protocols_allowed__mixed_not_allowed__openpgp_preferred()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = false;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::OpenPGP,
            {testKey("sender@example.net", GpgME::OpenPGP)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("prefer-smime@example.net"), {}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {
            GpgME::CMS,
            {testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {}},
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::CMS)}},
            },
        };

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();

        verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("openpgp button")), IsVisible, IsChecked);
        verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("smime button")), IsVisible, IsUnchecked);
        const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
        QCOMPARE(signingKeyWidgets.visible.size(), 1);
        QCOMPARE(signingKeyWidgets.hidden.size(), 1);
        QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP), preferredSolution.signingKeys[0].primaryFingerprint());
        QCOMPARE(signingKeyWidgets.hidden[0]->defaultKey(GpgME::CMS), alternativeSolution.signingKeys[0].primaryFingerprint());

        const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
        QCOMPARE(encryptionKeyWidgets.visible.size(), 3);
        QCOMPARE(encryptionKeyWidgets.hidden.size(), 3);

        // encryption key widgets for sender come first (visible for OpenPGP, hidden for S/MIME)
        QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP), preferredSolution.encryptionKeys.value(sender)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.hidden[0]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.hidden[0]->defaultKey(GpgME::CMS), alternativeSolution.encryptionKeys.value(sender)[0].primaryFingerprint());

        // encryption key widgets for other recipients follow (visible for OpenPGP, hidden for S/MIME)
        QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), "prefer-openpgp@example.net");
        QCOMPARE(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::OpenPGP),
                 preferredSolution.encryptionKeys.value(u"prefer-openpgp@example.net"_s)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.hidden[1]->property("address").toString(), "prefer-openpgp@example.net");
        QVERIFY(encryptionKeyWidgets.hidden[1]->defaultKey(GpgME::CMS).isEmpty());
        QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "prefer-smime@example.net");
        QVERIFY(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::OpenPGP).isEmpty());
        QCOMPARE(encryptionKeyWidgets.hidden[2]->property("address").toString(), "prefer-smime@example.net");
        QCOMPARE(encryptionKeyWidgets.hidden[2]->defaultKey(GpgME::CMS),
                 alternativeSolution.encryptionKeys.value(u"prefer-smime@example.net"_s)[0].primaryFingerprint());
    }

    void test__both_protocols_allowed__mixed_not_allowed__smime_preferred()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = false;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::CMS,
            {testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {}},
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::CMS)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {
            GpgME::OpenPGP,
            {testKey("sender@example.net", GpgME::OpenPGP)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("prefer-smime@example.net"), {}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP)}},
            },
        };

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();

        verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("openpgp button")), IsVisible, IsUnchecked);
        verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("smime button")), IsVisible, IsChecked);
        const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
        QCOMPARE(signingKeyWidgets.visible.size(), 1);
        QCOMPARE(signingKeyWidgets.hidden.size(), 1);
        QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::CMS), preferredSolution.signingKeys[0].primaryFingerprint());
        QCOMPARE(signingKeyWidgets.hidden[0]->defaultKey(GpgME::OpenPGP), alternativeSolution.signingKeys[0].primaryFingerprint());

        const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
        QCOMPARE(encryptionKeyWidgets.visible.size(), 3);
        QCOMPARE(encryptionKeyWidgets.hidden.size(), 3);

        // encryption key widgets for sender come first (visible for S/MIME, hidden for OpenPGP)
        QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::CMS), preferredSolution.encryptionKeys.value(sender)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.hidden[0]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.hidden[0]->defaultKey(GpgME::OpenPGP), alternativeSolution.encryptionKeys.value(sender)[0].primaryFingerprint());

        // encryption key widgets for other recipients follow (visible for OpenPGP, hidden for S/MIME)
        QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), "prefer-openpgp@example.net");
        QVERIFY(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::CMS).isEmpty());
        QCOMPARE(encryptionKeyWidgets.hidden[1]->property("address").toString(), "prefer-openpgp@example.net");
        QCOMPARE(encryptionKeyWidgets.hidden[1]->defaultKey(GpgME::OpenPGP),
                 alternativeSolution.encryptionKeys.value(u"prefer-openpgp@example.net"_s)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "prefer-smime@example.net");
        QCOMPARE(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::CMS),
                 preferredSolution.encryptionKeys.value(u"prefer-smime@example.net"_s)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.hidden[2]->property("address").toString(), "prefer-smime@example.net");
        QVERIFY(encryptionKeyWidgets.hidden[2]->defaultKey(GpgME::OpenPGP).isEmpty());
    }

    void test__openpgp_only()
    {
        const GpgME::Protocol forcedProtocol = GpgME::OpenPGP;
        const bool allowMixed = false;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::OpenPGP,
            {testKey("sender@example.net", GpgME::OpenPGP)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("prefer-smime@example.net"), {}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();

        verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("openpgp button")), IsHidden, IsChecked);
        verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("smime button")), IsHidden, IsUnchecked);
        const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
        QCOMPARE(signingKeyWidgets.visible.size(), 1);
        QCOMPARE(signingKeyWidgets.hidden.size(), 0);
        QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP), preferredSolution.signingKeys[0].primaryFingerprint());

        const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
        QCOMPARE(encryptionKeyWidgets.visible.size(), 3);
        QCOMPARE(encryptionKeyWidgets.hidden.size(), 0);

        // encryption key widget for sender comes first
        QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP), preferredSolution.encryptionKeys.value(sender)[0].primaryFingerprint());

        // encryption key widgets for other recipients follow
        QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), "prefer-openpgp@example.net");
        QCOMPARE(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::OpenPGP),
                 preferredSolution.encryptionKeys.value(u"prefer-openpgp@example.net"_s)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "prefer-smime@example.net");
        QVERIFY(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::OpenPGP).isEmpty());
    }

    void test__smime_only()
    {
        const GpgME::Protocol forcedProtocol = GpgME::CMS;
        const bool allowMixed = false;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::CMS,
            {testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {}},
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::CMS)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();

        verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("openpgp button")), IsHidden, IsUnchecked);
        verifyProtocolButton(dialog->findChild<QRadioButton *>(QStringLiteral("smime button")), IsHidden, IsChecked);
        const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
        QCOMPARE(signingKeyWidgets.visible.size(), 1);
        QCOMPARE(signingKeyWidgets.hidden.size(), 0);
        QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::CMS), preferredSolution.signingKeys[0].primaryFingerprint());

        const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
        QCOMPARE(encryptionKeyWidgets.visible.size(), 3);
        QCOMPARE(encryptionKeyWidgets.hidden.size(), 0);

        // encryption key widget for sender comes first
        QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::CMS), preferredSolution.encryptionKeys.value(sender)[0].primaryFingerprint());

        // encryption key widgets for other recipients follow
        QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), "prefer-openpgp@example.net");
        QVERIFY(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::CMS).isEmpty());
        QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "prefer-smime@example.net");
        QCOMPARE(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::CMS),
                 preferredSolution.encryptionKeys.value(u"prefer-smime@example.net"_s)[0].primaryFingerprint());
    }

    void test__both_protocols_allowed__mixed_allowed()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::UnknownProtocol,
            {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("unknown@example.net"), {}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();

        verifyProtocolButton(dialog->findChild<QCheckBox *>(QStringLiteral("openpgp button")), IsVisible, IsChecked);
        verifyProtocolButton(dialog->findChild<QCheckBox *>(QStringLiteral("smime button")), IsVisible, IsChecked);
        verifyProtocolLabels(dialog->findChildren<QLabel *>(QStringLiteral("protocol label")), 4, IsVisible);
        const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
        QCOMPARE(signingKeyWidgets.visible.size(), 2);
        QCOMPARE(signingKeyWidgets.hidden.size(), 0);
        QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP), preferredSolution.signingKeys[0].primaryFingerprint());
        QCOMPARE(signingKeyWidgets.visible[1]->defaultKey(GpgME::CMS), preferredSolution.signingKeys[1].primaryFingerprint());

        const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
        QCOMPARE(encryptionKeyWidgets.visible.size(), 5);
        QCOMPARE(encryptionKeyWidgets.hidden.size(), 0);

        // encryption key widgets for sender come first
        QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP), preferredSolution.encryptionKeys.value(sender)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::CMS), preferredSolution.encryptionKeys.value(sender)[1].primaryFingerprint());

        // encryption key widgets for other recipients follow
        QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "prefer-openpgp@example.net");
        QCOMPARE(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::UnknownProtocol),
                 preferredSolution.encryptionKeys.value(u"prefer-openpgp@example.net"_s)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.visible[3]->property("address").toString(), "prefer-smime@example.net");
        QCOMPARE(encryptionKeyWidgets.visible[3]->defaultKey(GpgME::UnknownProtocol),
                 preferredSolution.encryptionKeys.value(u"prefer-smime@example.net"_s)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.visible[4]->property("address").toString(), "unknown@example.net");
        QVERIFY(encryptionKeyWidgets.visible[4]->defaultKey(GpgME::UnknownProtocol).isEmpty());
    }

    void test__both_protocols_allowed__mixed_allowed__openpgp_only_preferred_solution()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::OpenPGP,
            {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("unknown@example.net"), {}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();

        verifyProtocolButton(dialog->findChild<QCheckBox *>(QStringLiteral("openpgp button")), IsVisible, IsChecked);
        verifyProtocolButton(dialog->findChild<QCheckBox *>(QStringLiteral("smime button")), IsVisible, IsUnchecked);
        verifyProtocolLabels(dialog->findChildren<QLabel *>(QStringLiteral("protocol label")), 4, IsHidden);
        const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
        QCOMPARE(signingKeyWidgets.visible.size(), 1);
        QCOMPARE(signingKeyWidgets.hidden.size(), 1);
        QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP), preferredSolution.signingKeys[0].primaryFingerprint());
        QCOMPARE(signingKeyWidgets.hidden[0]->defaultKey(GpgME::CMS), preferredSolution.signingKeys[1].primaryFingerprint());

        const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
        QCOMPARE(encryptionKeyWidgets.visible.size(), 3);
        QCOMPARE(encryptionKeyWidgets.hidden.size(), 1);

        // encryption key widgets for sender come first
        QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::OpenPGP), preferredSolution.encryptionKeys.value(sender)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.hidden[0]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.hidden[0]->defaultKey(GpgME::CMS), preferredSolution.encryptionKeys.value(sender)[1].primaryFingerprint());

        // encryption key widgets for other recipients follow
        QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), "prefer-openpgp@example.net");
        QCOMPARE(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::UnknownProtocol),
                 preferredSolution.encryptionKeys.value(u"prefer-openpgp@example.net"_s)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "unknown@example.net");
        QVERIFY(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::UnknownProtocol).isEmpty());
    }

    void test__both_protocols_allowed__mixed_allowed__smime_only_preferred_solution()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::CMS,
            {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("unknown@example.net"), {}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();

        verifyProtocolButton(dialog->findChild<QCheckBox *>(QStringLiteral("openpgp button")), IsVisible, IsUnchecked);
        verifyProtocolButton(dialog->findChild<QCheckBox *>(QStringLiteral("smime button")), IsVisible, IsChecked);
        verifyProtocolLabels(dialog->findChildren<QLabel *>(QStringLiteral("protocol label")), 4, IsHidden);
        const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
        QCOMPARE(signingKeyWidgets.visible.size(), 1);
        QCOMPARE(signingKeyWidgets.hidden.size(), 1);
        QCOMPARE(signingKeyWidgets.visible[0]->defaultKey(GpgME::CMS), preferredSolution.signingKeys[1].primaryFingerprint());
        QCOMPARE(signingKeyWidgets.hidden[0]->defaultKey(GpgME::OpenPGP), preferredSolution.signingKeys[0].primaryFingerprint());

        const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
        QCOMPARE(encryptionKeyWidgets.visible.size(), 3);
        QCOMPARE(encryptionKeyWidgets.hidden.size(), 1);

        // encryption key widgets for sender come first
        QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.visible[0]->defaultKey(GpgME::CMS), preferredSolution.encryptionKeys.value(sender)[1].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.hidden[0]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.hidden[0]->defaultKey(GpgME::OpenPGP), preferredSolution.encryptionKeys.value(sender)[0].primaryFingerprint());

        // encryption key widgets for other recipients follow
        QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), "prefer-smime@example.net");
        QCOMPARE(encryptionKeyWidgets.visible[1]->defaultKey(GpgME::UnknownProtocol),
                 preferredSolution.encryptionKeys.value(u"prefer-smime@example.net"_s)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "unknown@example.net");
        QVERIFY(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::UnknownProtocol).isEmpty());
    }

    void test__both_protocols_allowed__mixed_allowed__no_sender_keys()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::UnknownProtocol,
            {},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("unknown@example.net"), {}},
                {QStringLiteral("sender@example.net"), {}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();

        const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
        QCOMPARE(signingKeyWidgets.visible.size(), 2);
        QCOMPARE(signingKeyWidgets.hidden.size(), 0);

        const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
        QCOMPARE(encryptionKeyWidgets.visible.size(), 5);
        QCOMPARE(encryptionKeyWidgets.hidden.size(), 0);

        // encryption key widgets for sender come first
        QCOMPARE(encryptionKeyWidgets.visible[0]->property("address").toString(), sender);
        QCOMPARE(encryptionKeyWidgets.visible[1]->property("address").toString(), sender);

        // encryption key widgets for other recipients follow
        QCOMPARE(encryptionKeyWidgets.visible[2]->property("address").toString(), "prefer-openpgp@example.net");
        QCOMPARE(encryptionKeyWidgets.visible[2]->defaultKey(GpgME::UnknownProtocol),
                 preferredSolution.encryptionKeys.value(u"prefer-openpgp@example.net"_s)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.visible[3]->property("address").toString(), "prefer-smime@example.net");
        QCOMPARE(encryptionKeyWidgets.visible[3]->defaultKey(GpgME::UnknownProtocol),
                 preferredSolution.encryptionKeys.value(u"prefer-smime@example.net"_s)[0].primaryFingerprint());
        QCOMPARE(encryptionKeyWidgets.visible[4]->property("address").toString(), "unknown@example.net");
        QVERIFY(encryptionKeyWidgets.visible[4]->defaultKey(GpgME::UnknownProtocol).isEmpty());
    }

    void test__both_protocols_allowed__mixed_allowed__encrypt_only()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::UnknownProtocol,
            {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("unknown@example.net"), {}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, false, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();

        const auto signingKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("signing key")));
        QCOMPARE(signingKeyWidgets.visible.size(), 0);
        QCOMPARE(signingKeyWidgets.hidden.size(), 0);

        const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
        QCOMPARE(encryptionKeyWidgets.visible.size(), 5);
        QCOMPARE(encryptionKeyWidgets.hidden.size(), 0);
    }

    void test__ok_button_shows_generate_if_generate_is_selected()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::OpenPGP,
            {}, // no signing keys to get "Generate key" choice in OpenPGP combo
            {{QStringLiteral("sender@example.net"), {}}} // no encryption keys to get "Generate key" choice in OpenPGP combo
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();
        waitForKeySelectionCombosBeingInitialized(dialog.get());

        const auto okButton = dialog->findChild<QPushButton *>("ok button");
        QVERIFY(okButton);
        QVERIFY(okButton->text() != u"Generate"_s);

        {
            // get the first signing key combo which is the OpenPGP one
            const auto signingKeyCombo = dialog->findChild<KeySelectionCombo *>("signing key");
            verifyWidgetVisibility(signingKeyCombo, IsVisible);
            const auto originalIndex = signingKeyCombo->currentIndex();
            const auto generateIndex = signingKeyCombo->findData(GenerateKey);
            QVERIFY(generateIndex != -1);
            signingKeyCombo->setCurrentIndex(generateIndex);
            QCOMPARE(okButton->text(), "Generate");
            signingKeyCombo->setCurrentIndex(originalIndex);
            QVERIFY(okButton->text() != u"Generate"_s);
        }
        {
            // get the first encryption key combo which is the OpenPGP one for the sender
            const auto encryptionKeyCombo = dialog->findChild<KeySelectionCombo *>("encryption key");
            verifyWidgetVisibility(encryptionKeyCombo, IsVisible);
            const auto originalIndex = encryptionKeyCombo->currentIndex();
            const auto generateIndex = encryptionKeyCombo->findData(GenerateKey);
            QVERIFY(generateIndex != -1);
            encryptionKeyCombo->setCurrentIndex(generateIndex);
            QCOMPARE(okButton->text(), QStringLiteral("Generate"));
            encryptionKeyCombo->setCurrentIndex(originalIndex);
            QVERIFY(okButton->text() != QStringLiteral("Generate"));
        }
    }

    void test__ok_button_does_not_show_generate_if_generate_is_selected_in_hidden_combos()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::CMS, // enables S/MIME as default protocol, hides OpenPGP combos
            {}, // no signing keys to get "Generate key" choice in OpenPGP combo
            {{QStringLiteral("sender@example.net"), {}}} // no encryption keys to get "Generate key" choice in OpenPGP combo
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();
        waitForKeySelectionCombosBeingInitialized(dialog.get());

        const auto okButton = dialog->findChild<QPushButton *>("ok button");
        QVERIFY(okButton);
        QVERIFY(okButton->text() != u"Generate"_s);

        {
            // get the first signing key combo which is the OpenPGP one
            const auto signingKeyCombo = dialog->findChild<KeySelectionCombo *>("signing key");
            verifyWidgetVisibility(signingKeyCombo, IsHidden);
            const auto originalIndex = signingKeyCombo->currentIndex();
            const auto generateIndex = signingKeyCombo->findData(GenerateKey);
            QVERIFY(generateIndex != -1);
            signingKeyCombo->setCurrentIndex(generateIndex);
            QVERIFY(okButton->text() != QStringLiteral("Generate"));
            signingKeyCombo->setCurrentIndex(originalIndex);
            QVERIFY(okButton->text() != QStringLiteral("Generate"));
        }
        {
            // get the first encryption key combo which is the OpenPGP one for the sender
            const auto encryptionKeyCombo = dialog->findChild<KeySelectionCombo *>("encryption key");
            verifyWidgetVisibility(encryptionKeyCombo, IsHidden);
            const auto originalIndex = encryptionKeyCombo->currentIndex();
            const auto generateIndex = encryptionKeyCombo->findData(GenerateKey);
            QVERIFY(generateIndex != -1);
            encryptionKeyCombo->setCurrentIndex(generateIndex);
            QVERIFY(okButton->text() != QStringLiteral("Generate"));
            encryptionKeyCombo->setCurrentIndex(originalIndex);
            QVERIFY(okButton->text() != QStringLiteral("Generate"));
        }
    }

    void test__ok_button_is_disabled_if_ignore_is_selected_in_all_visible_encryption_combos()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::OpenPGP,
            {}, // no signing keys to get "Generate key" choice in OpenPGP combo
            {{QStringLiteral("sender@example.net"), {}}} // no encryption keys to get "Generate key" choice in OpenPGP combo
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();
        waitForKeySelectionCombosBeingInitialized(dialog.get());

        const auto okButton = dialog->findChild<QPushButton *>(QStringLiteral("ok button"));
        QVERIFY(okButton);
        QVERIFY(okButton->isEnabled());

        const auto encryptionKeyWidgets = visibleAndHiddenWidgets(dialog->findChildren<KeySelectionCombo *>(QStringLiteral("encryption key")));
        for (auto combo : encryptionKeyWidgets.visible) {
            const auto ignoreIndex = combo->findData(IgnoreKey);
            QVERIFY(ignoreIndex != -1);
            combo->setCurrentIndex(ignoreIndex);
        }
        QVERIFY(!okButton->isEnabled());
    }

    void test__vs_de_compliance__all_keys_fully_valid()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::UnknownProtocol,
            {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        Tests::FakeCryptoConfigStringValue fakeCompliance{"gpg", "compliance", QStringLiteral("de-vs")};
        Tests::FakeCryptoConfigIntValue fakeDeVsCompliance{"gpg", "compliance_de_vs", 1};
        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();
        waitForKeySelectionCombosBeingInitialized(dialog.get());

        const auto complianceLabel = dialog->findChild<QLabel *>(QStringLiteral("compliance label"));
        verifyWidgetVisibility(complianceLabel, IsVisible);
        QVERIFY(!complianceLabel->text().contains(DeVSCompliance::name(false)));
    }

    void test__vs_de_compliance__not_all_keys_fully_valid()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::UnknownProtocol,
            {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("marginal-openpgp@example.net"), {testKey("Marginal Validity <marginal-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        Tests::FakeCryptoConfigStringValue fakeCompliance{"gpg", "compliance", QStringLiteral("de-vs")};
        Tests::FakeCryptoConfigIntValue fakeDeVsCompliance{"gpg", "compliance_de_vs", 1};
        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();
        waitForKeySelectionCombosBeingInitialized(dialog.get());

        const auto complianceLabel = dialog->findChild<QLabel *>(QStringLiteral("compliance label"));
        verifyWidgetVisibility(complianceLabel, IsVisible);
        QVERIFY(complianceLabel->text().contains(DeVSCompliance::name(false)));
    }

    void test__vs_de_compliance__null_keys_are_ignored()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::UnknownProtocol,
            {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("unknown@example.net"), {}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        Tests::FakeCryptoConfigStringValue fakeCompliance{"gpg", "compliance", QStringLiteral("de-vs")};
        Tests::FakeCryptoConfigIntValue fakeDeVsCompliance{"gpg", "compliance_de_vs", 1};
        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();
        waitForKeySelectionCombosBeingInitialized(dialog.get());

        const auto complianceLabel = dialog->findChild<QLabel *>(QStringLiteral("compliance label"));
        verifyWidgetVisibility(complianceLabel, IsVisible);
        QVERIFY(!complianceLabel->text().contains(DeVSCompliance::name(false)));
    }

    void test__sign_and_encrypt_to_self_only()
    {
        const GpgME::Protocol forcedProtocol = GpgME::OpenPGP;
        const bool allowMixed = false;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::OpenPGP,
            {testKey("sender@example.net", GpgME::OpenPGP)},
            {
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();

        QVERIFY(!dialog->findChild<QGroupBox *>(QStringLiteral("encrypt-to-others box")));
    }

    void test__sign_and_encrypt_to_self_and_others()
    {
        const GpgME::Protocol forcedProtocol = GpgME::OpenPGP;
        const bool allowMixed = false;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::OpenPGP,
            {testKey("sender@example.net", GpgME::OpenPGP)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();

        QVERIFY(dialog->findChild<QGroupBox *>(QStringLiteral("encrypt-to-others box")));
    }

    void test__result_does_not_include_null_keys()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("unknown@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::UnknownProtocol,
            {},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("unknown@example.net"), {}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();
        waitForKeySelectionCombosBeingInitialized(dialog.get());
        switchKeySelectionCombosFromGenerateKeyToIgnoreKey(dialog->findChildren<KeySelectionCombo *>());

        const QSignalSpy dialogAcceptedSpy{dialog.get(), &QDialog::accepted};
        QVERIFY(dialogAcceptedSpy.isValid());

        const auto okButton = dialog->findChild<QPushButton *>(QStringLiteral("ok button"));
        QVERIFY(okButton);
        QVERIFY(okButton->isEnabled());
        okButton->click();

        QCOMPARE(dialogAcceptedSpy.count(), 1);
        const KeyResolver::Solution expectedSolution = {
            GpgME::UnknownProtocol,
            {},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
            }};
        verifySolution(dialog->result(), expectedSolution);
    }

    void test__result_has_keys_for_both_protocols_if_both_are_needed()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::UnknownProtocol,
            {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();
        waitForKeySelectionCombosBeingInitialized(dialog.get());
        switchKeySelectionCombosFromGenerateKeyToIgnoreKey(dialog->findChildren<KeySelectionCombo *>());

        const QSignalSpy dialogAcceptedSpy{dialog.get(), &QDialog::accepted};
        QVERIFY(dialogAcceptedSpy.isValid());

        const auto okButton = dialog->findChild<QPushButton *>(QStringLiteral("ok button"));
        QVERIFY(okButton);
        QVERIFY(okButton->isEnabled());
        okButton->click();

        QCOMPARE(dialogAcceptedSpy.count(), 1);
        verifySolution(dialog->result(), preferredSolution);
    }

    void test__result_has_only_openpgp_keys_if_openpgp_protocol_selected()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::UnknownProtocol,
            {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();
        waitForKeySelectionCombosBeingInitialized(dialog.get());
        switchKeySelectionCombosFromGenerateKeyToIgnoreKey(dialog->findChildren<KeySelectionCombo *>());

        const auto smimeButton = dialog->findChild<QCheckBox *>(QStringLiteral("smime button"));
        QVERIFY(smimeButton);
        smimeButton->click();
        QVERIFY(!smimeButton->isChecked());

        const QSignalSpy dialogAcceptedSpy{dialog.get(), &QDialog::accepted};
        QVERIFY(dialogAcceptedSpy.isValid());

        const auto okButton = dialog->findChild<QPushButton *>(QStringLiteral("ok button"));
        QVERIFY(okButton);
        QVERIFY(okButton->isEnabled());
        okButton->click();

        QCOMPARE(dialogAcceptedSpy.count(), 1);
        const KeyResolver::Solution expectedSolution = {
            GpgME::OpenPGP,
            {testKey("sender@example.net", GpgME::OpenPGP)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP)}},
            }};
        verifySolution(dialog->result(), expectedSolution);
    }

    void test__result_has_only_smime_keys_if_smime_protocol_selected()
    {
        const GpgME::Protocol forcedProtocol = GpgME::UnknownProtocol;
        const bool allowMixed = true;
        const QString sender = QStringLiteral("sender@example.net");
        const KeyResolver::Solution preferredSolution = {
            GpgME::UnknownProtocol,
            {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("prefer-openpgp@example.net"), {testKey("Full Trust <prefer-openpgp@example.net>", GpgME::OpenPGP)}},
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::OpenPGP), testKey("sender@example.net", GpgME::CMS)}},
            },
        };
        const KeyResolver::Solution alternativeSolution = {};

        const auto dialog = std::make_unique<NewKeyApprovalDialog>(true, true, sender, preferredSolution, alternativeSolution, allowMixed, forcedProtocol);
        dialog->show();
        waitForKeySelectionCombosBeingInitialized(dialog.get());
        switchKeySelectionCombosFromGenerateKeyToIgnoreKey(dialog->findChildren<KeySelectionCombo *>());

        const auto openPGPButton = dialog->findChild<QCheckBox *>(QStringLiteral("openpgp button"));
        QVERIFY(openPGPButton);
        openPGPButton->click();
        QVERIFY(!openPGPButton->isChecked());

        const QSignalSpy dialogAcceptedSpy{dialog.get(), &QDialog::accepted};
        QVERIFY(dialogAcceptedSpy.isValid());

        const auto okButton = dialog->findChild<QPushButton *>(QStringLiteral("ok button"));
        QVERIFY(okButton);
        QVERIFY(okButton->isEnabled());
        okButton->click();

        QCOMPARE(dialogAcceptedSpy.count(), 1);
        const KeyResolver::Solution expectedSolution = {
            GpgME::CMS,
            {testKey("sender@example.net", GpgME::CMS)},
            {
                {QStringLiteral("prefer-smime@example.net"), {testKey("Trusted S/MIME <prefer-smime@example.net>", GpgME::CMS)}},
                {QStringLiteral("sender@example.net"), {testKey("sender@example.net", GpgME::CMS)}},
            }};
        verifySolution(dialog->result(), expectedSolution);
    }

private:
    std::shared_ptr<const KeyCache> mKeyCache;
};

QTEST_MAIN(NewKeyApprovalDialogTest)
#include "newkeyapprovaldialogtest.moc"
