1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
|
/*
SPDX-FileCopyrightText: 2019 Glen Ditchfield <GJDitchfield@acm.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "replystrategytest.h"
#include <QSignalSpy>
#include <QStandardPaths>
#include <QStringLiteral>
#include <QTest>
#include <KIdentityManagementCore/Identity>
#include <KIdentityManagementCore/IdentityManager>
#include <MessageComposer/Composer>
#include <MessageComposer/GlobalPart>
#include <MessageComposer/InfoPart>
#include <MessageComposer/TextPart>
const auto defaultAddress{QStringLiteral("default@example.org")};
const auto nondefaultAddress{QStringLiteral("nondefault@example.com")};
const auto friend1Address{QStringLiteral("friend1@example.net")};
const auto friend2Address{QStringLiteral("friend2@example.net")};
const auto replyAddress{QStringLiteral("reply@example.com")};
const auto followupAddress{QStringLiteral("followup@example.org")};
const auto listAddress{QStringLiteral("list@example.com")};
const auto mailReplyAddress{QStringLiteral("mailreply@example.com")};
const QStringList nobody{};
static inline const QStringList only(const QString &address)
{
return QStringList{address};
}
static inline const QStringList both(const QString &address1, const QString &address2)
{
return QStringList{address1, address2};
}
using namespace MessageComposer;
static KMime::Message::Ptr basicMessage(const QString &fromAddress, const QStringList &toAddresses)
{
Composer composer;
composer.infoPart()->setFrom(fromAddress);
composer.infoPart()->setTo(toAddresses);
composer.infoPart()->setSubject(QStringLiteral("Test Email Subject"));
composer.textPart()->setWrappedPlainText(QStringLiteral("Test email body."));
composer.exec();
return composer.resultMessages().first();
}
#define COMPARE_ADDRESSES(actual, expected) \
if (!compareAddresses(actual, expected)) { \
QFAIL(qPrintable(QStringLiteral("%1 is \"%2\"").arg(QString::fromLatin1(#actual), actual->displayString()))); \
return; \
}
template<class T>
bool compareAddresses(const T *actual, const QStringList &expected)
{
auto addresses{actual->addresses()};
if (addresses.length() != expected.length()) {
return false;
}
for (const auto &e : expected) {
if (!addresses.contains(e.toLatin1())) {
return false;
}
}
return true;
}
ReplyStrategyTest::ReplyStrategyTest(QObject *parent)
: QObject(parent)
{
QStandardPaths::setTestModeEnabled(true);
}
ReplyStrategyTest::~ReplyStrategyTest()
{
// Workaround QTestLib not flushing deleteLater()s on exit, which
// leads to WebEngine asserts (view not deleted)
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
}
void ReplyStrategyTest::initTestCase()
{
QFile::remove(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/emailidentities"));
QFile::remove(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/emaildefaults"));
mIdentityManager = new KIdentityManagementCore::IdentityManager;
auto homeIdentity =
mIdentityManager->newFromExisting(KIdentityManagementCore::Identity{QStringLiteral("Home Identity"), QStringLiteral("Full Home Name"), defaultAddress});
QVERIFY(mIdentityManager->setAsDefault(homeIdentity.uoid()));
auto workIdentity = mIdentityManager->newFromExisting(
KIdentityManagementCore::Identity{QStringLiteral("Work Identity"), QStringLiteral("Full Work Name"), nondefaultAddress});
mIdentityManager->commit();
}
void ReplyStrategyTest::cleanupTestCase()
{
delete mIdentityManager;
}
KMime::Message::Ptr ReplyStrategyTest::makeReply(const KMime::Message::Ptr &original, const ReplyStrategy strategy)
{
MessageFactoryNG factory{original, 0};
factory.setReplyStrategy(strategy);
factory.setIdentityManager(mIdentityManager);
QSignalSpy spy{&factory, &MessageFactoryNG::createReplyDone};
factory.createReplyAsync();
KMime::Message::Ptr result{nullptr};
[&] {
QVERIFY(spy.wait());
QCOMPARE(spy.count(), 1);
result = spy.at(0).at(0).value<MessageFactoryNG::MessageReply>().msg;
}();
return result;
}
void ReplyStrategyTest::testReply_data()
{
QTest::addColumn<QString>("oFrom"); // Original message's From address.
QTest::addColumn<QStringList>("oTo"); // Original message's To addresses.
QTest::addColumn<QStringList>("oCc"); // Original message's CC addresses.
QTest::addColumn<QStringList>("oRT"); // Original message's Reply-To addresses.
QTest::addColumn<QStringList>("oMFT"); // Original message's Mail-Followup-To addresses.
QTest::addColumn<QString>("oLP"); // Original message's List-Post address.
QTest::addColumn<QStringList>("oMRT"); // Original message's Mail-Reply-To addresses.
QTest::addColumn<int>("strategy"); // ReplyStrategy (passed as an int).
QTest::addColumn<QString>("rFrom"); // Reply's expected From address.
QTest::addColumn<QStringList>("rTo"); // Reply's expected To addresses.
QTest::addColumn<QStringList>("rCc"); // Reply's expected CC addresses.
// Smart Replies
// -------------
// Smart Reply does not set CC headers. (Compare ReplyAll.)
// ReplySmart uses Mail-Reply-To, Reply-To, or From (in that order)
// for the original's author's address, if List-Post is absent.
QTest::newRow("ReplySmart, from someone to default identity") << friend1Address << only(defaultAddress) << only(friend2Address) << nobody << nobody
<< QString() << nobody << (int)ReplySmart << defaultAddress << only(friend1Address) << nobody;
QTest::newRow("ReplySmart, from someone to non-default identity")
<< friend1Address << both(friend2Address, nondefaultAddress) << only(defaultAddress) << nobody << nobody << QString() << nobody << (int)ReplySmart
<< nondefaultAddress << only(friend1Address) << nobody;
QTest::newRow("ReplySmart, from someone with Reply-To")
<< friend1Address << only(defaultAddress) << only(friend2Address) << both(replyAddress, friend2Address) << nobody << QString() << nobody
<< (int)ReplySmart << defaultAddress << both(friend2Address, replyAddress) << nobody;
QTest::newRow("ReplySmart, from someone with Mail-Reply-To")
<< friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody << QString() << only(mailReplyAddress)
<< (int)ReplySmart << defaultAddress << only(mailReplyAddress) << nobody;
// If the original message was _from_ the user _to_ another person (the
// reverse of the usual direction), a smart reply goes to the other person.
// Therefore Mail-Reply-To and Reply-To are ignored.
// The reply is assumed to add to the original message.
QTest::newRow("ReplySmart, from default identity to someone") << defaultAddress << only(friend1Address) << only(friend2Address) << nobody << nobody
<< QString() << nobody << (int)ReplySmart << defaultAddress << only(friend1Address) << nobody;
QTest::newRow("ReplySmart, from default identity with Reply-To to someone")
<< defaultAddress << only(friend1Address) << only(friend2Address) << only(replyAddress) << nobody << QString() << only(mailReplyAddress)
<< (int)ReplySmart << defaultAddress << only(friend1Address) << nobody;
// If the original message was from one of the user's identities to another
// identity (i.e., between two of the user's mail accounts), a smart reply
// goes back to the sending identity.
QTest::newRow("ReplySmart, between identities") << defaultAddress << only(nondefaultAddress) << only(friend2Address) << nobody << nobody << QString()
<< nobody << (int)ReplySmart << nondefaultAddress << only(defaultAddress) << nobody;
// If the original message appears to be from a mailing list, smart replies
// go to the Mail-Followup-To, Reply-To, or List-Post addresses, in that
// order of preference.
QTest::newRow("ReplySmart, from list with Mail-Followup-To")
<< friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << only(followupAddress) << listAddress << nobody
<< (int)ReplySmart << defaultAddress << only(followupAddress) << nobody;
QTest::newRow("ReplySmart, from list with Reply-To") << friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody
<< listAddress << nobody << (int)ReplySmart << defaultAddress << only(replyAddress) << nobody;
QTest::newRow("ReplySmart, from list with List-Post")
<< friend1Address << only(nondefaultAddress) << only(friend2Address) << nobody << nobody << listAddress << only(mailReplyAddress) << (int)ReplySmart
<< nondefaultAddress << only(listAddress) << nobody;
// Replies to Mailing Lists
// ------------------------
// If the original message has a Mail-Followup-To header, replies to the list
// go to the followup address, in preference to List-Post and Reply-To.
// Cc and Mail-Reply-To are ignored.
QTest::newRow("ReplyList, from list with Mail-Followup-To")
<< friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << only(followupAddress) << listAddress
<< only(mailReplyAddress) << (int)ReplyList << defaultAddress << only(followupAddress) << nobody;
// If the original message has a List-Post header, replies to the list
// go to that address, in preference to Reply-To.
QTest::newRow("ReplyList, from list with List-Post") << friend1Address << only(defaultAddress) << nobody << only(replyAddress) << nobody << listAddress
<< nobody << (int)ReplyList << defaultAddress << only(listAddress) << nobody;
// If the original message has just a Reply-To header, assume the list
// [munges Reply-To](https://www.gnu.org/software/mailman/mailman-admin/node11.html)
/// and send the reply to that address.
QTest::newRow("ReplyList, from list with Reply-To") << friend1Address << only(defaultAddress) << nobody << only(replyAddress) << nobody << QString()
<< nobody << (int)ReplyList << defaultAddress << only(replyAddress) << nobody;
// If the original message has neither Mail-Followup-To, List-Post, nor
// Reply-To headers, replies to the list do not choose a To address.
QTest::newRow("ReplyList, from list with no headers")
<< friend1Address << only(defaultAddress) << nobody << nobody << nobody << QString() << nobody << (int)ReplyList << defaultAddress << nobody << nobody;
// Replies to All
// --------------
// ReplyAll adds CC addresses to the reply for the original's recipients,
// except for the user's identities.
QTest::newRow("ReplyAll, with Cc in original") << friend1Address << only(defaultAddress) << both(friend2Address, nondefaultAddress) << nobody << nobody
<< QString() << nobody << (int)ReplyAll << defaultAddress << only(friend1Address) << only(friend2Address);
QTest::newRow("ReplyAll, with multiple To addresses in original")
<< friend1Address << both(friend2Address, nondefaultAddress) << only(defaultAddress) << nobody << nobody << QString() << nobody << (int)ReplyAll
<< nondefaultAddress << both(friend1Address, friend2Address) << nobody;
QTest::newRow("ReplyAll, with Reply-To in original")
<< friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody << QString() << nobody << (int)ReplyAll
<< defaultAddress << only(replyAddress) << only(friend2Address);
QTest::newRow("ReplyAll, with Mail-Reply-To in original")
<< friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody << QString() << only(mailReplyAddress)
<< (int)ReplyAll << defaultAddress << only(mailReplyAddress) << only(friend2Address);
// If the original message was _from_ the user _to_ another person (the
// reverse of the usual direction), reply to all goes to the other person.
// Therefore Mail-Reply-To and Reply-To are ignored.
// The reply is assumed to add to the original message.
QTest::newRow("ReplyAll, from default identity to someone")
<< defaultAddress << only(friend1Address) << only(friend2Address) << only(replyAddress) << nobody << QString() << only(mailReplyAddress)
<< (int)ReplyAll << defaultAddress << only(friend1Address) << only(friend2Address);
// If the original message was from one of the user's identities to another
// identity (i.e., between two of the user's mail accounts), reply to all
// goes back to the sending identity.
QTest::newRow("ReplyAll, between identities") << defaultAddress << only(nondefaultAddress) << only(friend2Address) << nobody << nobody << QString()
<< nobody << (int)ReplyAll << nondefaultAddress << only(defaultAddress) << only(friend2Address);
// If the original passed through a mailing list, ReplyAll replies to the
// list.
// It CCs the author, using Mail-Reply-To, Reply-To, or From (in that order).
QTest::newRow("ReplyAll, from list with List-Post")
<< friend1Address << only(nondefaultAddress) << only(friend2Address) << nobody << nobody << listAddress << nobody << (int)ReplyAll << nondefaultAddress
<< only(listAddress) << both(friend1Address, friend2Address);
QTest::newRow("ReplyAll, from list with Reply-To")
<< friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody << listAddress << nobody << (int)ReplyAll
<< defaultAddress << only(listAddress) << both(replyAddress, friend2Address);
QTest::newRow("ReplyAll, from list with Mail-Reply-To")
<< friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody << listAddress << only(mailReplyAddress)
<< (int)ReplyAll << defaultAddress << only(listAddress) << both(mailReplyAddress, friend2Address);
// If Reply-To is the same as List-Post, ReplyAll ignores it and uses
// From for the author's address, because the mailing list munged Reply-To.
QTest::newRow("ReplyAll, from list that munges Reply-To") << friend1Address << only(defaultAddress) << nobody << only(listAddress) << nobody << listAddress
<< nobody << (int)ReplyAll << defaultAddress << only(listAddress) << only(friend1Address);
// If Reply-To contains List-Post, ReplyAll uses the other reply
// addresses, because the mailing list didn't completely munge Reply-To.
QTest::newRow("ReplyAll, from list that lightly munges Reply-To")
<< friend1Address << only(defaultAddress) << nobody << both(replyAddress, listAddress) << nobody << listAddress << nobody << (int)ReplyAll
<< defaultAddress << only(listAddress) << only(replyAddress);
// If Mail-Followup-To header is present, use it for To and ignore other
// headers. Cc is empty.
QTest::newRow("ReplyAll, from list with Reply-To and Mail-Followup-To")
<< friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << only(followupAddress) << listAddress
<< only(mailReplyAddress) << (int)ReplyAll << defaultAddress << only(followupAddress) << nobody;
// Reply to Author
// ---------------
// ReplyAuthor ignores Cc, and replies to the Mail-Reply-To, Reply-To, or
// From addresses, in that order of preference, if List-Post is absent.
QTest::newRow("ReplyAuthor, no special headers") << friend1Address << only(defaultAddress) << only(friend2Address) << nobody << nobody << QString()
<< nobody << (int)ReplyAuthor << defaultAddress << only(friend1Address) << nobody;
QTest::newRow("ReplyAuthor, from someone with Reply-To") << friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody
<< QString() << nobody << (int)ReplyAuthor << defaultAddress << only(replyAddress) << nobody;
QTest::newRow("ReplyAuthor, from someone with Mail-Reply-To")
<< friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << nobody << QString() << only(mailReplyAddress)
<< (int)ReplyAuthor << defaultAddress << only(mailReplyAddress) << nobody;
// If Reply-To is the same as List-Post, ReplyAuthor ignores it and uses
// From, because the mailing list munged Reply-To.
QTest::newRow("ReplyAuthor, from list that munges Reply-To")
<< friend1Address << only(defaultAddress) << only(friend2Address) << only(listAddress) << nobody << listAddress << nobody << (int)ReplyAuthor
<< defaultAddress << only(friend1Address) << nobody;
// If Reply-To contains List-Post, ReplyAuthor uses the other reply
// addresses, because the mailing list didn't completely munge Reply-To.
QTest::newRow("ReplyAuthor, from list that lightly munges Reply-To")
<< friend1Address << only(defaultAddress) << only(friend2Address) << both(listAddress, replyAddress) << nobody << listAddress << nobody
<< (int)ReplyAuthor << defaultAddress << only(replyAddress) << nobody;
// Reply to None
// -------------
// ReplyNone ignores all possible headers and does not choose a To address.
QTest::newRow("ReplyNone") << friend1Address << only(defaultAddress) << only(friend2Address) << only(replyAddress) << only(followupAddress) << listAddress
<< only(mailReplyAddress) << (int)ReplyNone << defaultAddress << nobody << nobody;
}
void ReplyStrategyTest::testReply()
{
QFETCH(const QString, oFrom);
QFETCH(const QStringList, oTo);
QFETCH(const QStringList, oCc);
QFETCH(const QStringList, oRT);
QFETCH(const QStringList, oMFT);
QFETCH(const QString, oLP);
QFETCH(const QStringList, oMRT);
QFETCH(const int, strategy);
QFETCH(const QString, rFrom);
QFETCH(const QStringList, rTo);
QFETCH(const QStringList, rCc);
auto original{basicMessage(oFrom, oTo)};
if (!oCc.isEmpty()) {
auto cc{new KMime::Headers::Cc};
for (const auto &a : oCc) {
cc->addAddress(a.toLatin1());
}
original->setHeader(cc);
}
if (!oRT.isEmpty()) {
auto replyTo{new KMime::Headers::ReplyTo};
for (const auto &a : oRT) {
replyTo->addAddress(a.toLatin1());
}
original->setHeader(replyTo);
}
if (!oMFT.isEmpty()) {
auto mailFollowupTo = new KMime::Headers::Generic("Mail-Followup-To");
mailFollowupTo->from7BitString(oMFT.join(QLatin1Char(',')).toLatin1());
original->setHeader(mailFollowupTo);
}
if (!oLP.isEmpty()) {
auto listPost = new KMime::Headers::Generic("List-Post");
listPost->from7BitString(QByteArray("<mailto:" + oLP.toLatin1() + ">"));
original->setHeader(listPost);
}
if (!oMRT.isEmpty()) {
auto mailReplyTo = new KMime::Headers::Generic("Mail-Reply-To");
mailReplyTo->from7BitString(oMRT.join(QLatin1Char(',')).toLatin1());
original->setHeader(mailReplyTo);
}
if (auto reply = makeReply(original, (ReplyStrategy)strategy)) {
COMPARE_ADDRESSES(reply->from(), only(rFrom));
COMPARE_ADDRESSES(reply->to(), rTo);
COMPARE_ADDRESSES(reply->cc(), rCc);
}
original.clear();
}
QTEST_MAIN(ReplyStrategyTest)
#include "moc_replystrategytest.cpp"
|