File indexing completed on 2024-05-12 05:10:39

0001 /*
0002     SPDX-FileCopyrightText: 2013 Sérgio Martins <iamsergio@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "itiphandlertest.h"
0008 #include "fetchjobcalendar.h"
0009 #include "helper.h"
0010 #include "mailclient_p.h"
0011 #include "utils_p.h"
0012 
0013 #include <Akonadi/CollectionFetchJob>
0014 #include <Akonadi/CollectionFetchScope>
0015 #include <Akonadi/ItemDeleteJob>
0016 #include <Akonadi/ItemFetchScope>
0017 #include <Akonadi/MessageQueueJob>
0018 #include <KCalendarCore/Attendee>
0019 #include <KCalendarCore/ICalFormat>
0020 #include <akonadi/qtest_akonadi.h>
0021 
0022 #include <QString>
0023 #include <QTimeZone>
0024 
0025 using namespace Akonadi;
0026 using namespace KCalendarCore;
0027 
0028 Q_DECLARE_METATYPE(Akonadi::IncidenceChanger::InvitationPolicy)
0029 Q_DECLARE_METATYPE(QList<Akonadi::IncidenceChanger::ChangeType>)
0030 Q_DECLARE_METATYPE(Akonadi::ITIPHandler::Result)
0031 Q_DECLARE_METATYPE(KCalendarCore::Attendee::PartStat)
0032 Q_DECLARE_METATYPE(QList<int>)
0033 
0034 static const char *s_ourEmail = "unittests@dev.nul"; // change also in kdepimlibs/akonadi/calendar/tests/unittestenv/kdehome/share/config
0035 static const char *s_outEmail2 = "identity2@kde.org";
0036 
0037 class FakeMessageQueueJob : public Akonadi::MessageQueueJob
0038 {
0039 public:
0040     explicit FakeMessageQueueJob(QObject *parent = nullptr)
0041         : Akonadi::MessageQueueJob(parent)
0042     {
0043     }
0044 
0045     void start() override
0046     {
0047         UnitTestResult unitTestResult;
0048         unitTestResult.message = message();
0049         unitTestResult.from = addressAttribute().from();
0050         unitTestResult.to = addressAttribute().to();
0051         unitTestResult.cc = addressAttribute().cc();
0052         unitTestResult.bcc = addressAttribute().bcc();
0053         unitTestResult.transportId = transportAttribute().transportId();
0054         FakeMessageQueueJob::sUnitTestResults << unitTestResult;
0055 
0056         setError(Akonadi::MailClient::ResultSuccess);
0057         setErrorText(QString());
0058 
0059         emitResult();
0060     }
0061 
0062     static UnitTestResult::List sUnitTestResults;
0063 };
0064 
0065 UnitTestResult::List FakeMessageQueueJob::sUnitTestResults;
0066 
0067 class FakeITIPHandlerComponentFactory : public ITIPHandlerComponentFactory
0068 {
0069 public:
0070     FakeITIPHandlerComponentFactory(QObject *parent = nullptr)
0071         : ITIPHandlerComponentFactory(parent)
0072     {
0073     }
0074 
0075     Akonadi::MessageQueueJob *createMessageQueueJob(const KCalendarCore::IncidenceBase::Ptr &incidence,
0076                                                     const KIdentityManagementCore::Identity &identity,
0077                                                     QObject *parent = nullptr) override
0078     {
0079         Q_UNUSED(incidence)
0080         Q_UNUSED(identity)
0081         return new FakeMessageQueueJob(parent);
0082     }
0083 };
0084 
0085 void ITIPHandlerTest::initTestCase()
0086 {
0087     AkonadiTest::checkTestIsIsolated();
0088     extern AKONADI_CALENDAR_TESTS_EXPORT bool akonadi_calendar_running_unittests;
0089     akonadi_calendar_running_unittests = true;
0090     m_pendingItipMessageSignal = 0;
0091     m_pendingIncidenceChangerSignal = 0;
0092     m_itipHandler = nullptr;
0093     m_cancelExpected = false;
0094     m_changer = new IncidenceChanger(new FakeITIPHandlerComponentFactory(this), this);
0095     m_changer->setHistoryEnabled(false);
0096     m_changer->setGroupwareCommunication(true);
0097     m_changer->setInvitationPolicy(IncidenceChanger::InvitationPolicySend); // don't show dialogs
0098 
0099     connect(m_changer, &IncidenceChanger::createFinished, this, &ITIPHandlerTest::onCreateFinished);
0100 
0101     connect(m_changer, &IncidenceChanger::deleteFinished, this, &ITIPHandlerTest::onDeleteFinished);
0102 
0103     connect(m_changer, &IncidenceChanger::modifyFinished, this, &ITIPHandlerTest::onModifyFinished);
0104 }
0105 
0106 void ITIPHandlerTest::testProcessITIPMessage_data()
0107 {
0108     QTest::addColumn<QString>("data_filename");
0109     QTest::addColumn<QString>("action");
0110     QTest::addColumn<QString>("receiver");
0111     QTest::addColumn<QString>("incidenceUid"); // uid of incidence in invitation
0112     QTest::addColumn<Akonadi::ITIPHandler::Result>("expectedResult");
0113     QTest::addColumn<int>("expectedNumIncidences");
0114     QTest::addColumn<KCalendarCore::Attendee::PartStat>("expectedPartStat");
0115 
0116     QString data_filename;
0117     QString action = QStringLiteral("accepted");
0118     QString incidenceUid = QStringLiteral("uosj936i6arrtl9c2i5r2mfuvg");
0119     QString receiver = QLatin1StringView(s_ourEmail);
0120     Akonadi::ITIPHandler::Result expectedResult;
0121     int expectedNumIncidences = 0;
0122     KCalendarCore::Attendee::PartStat expectedPartStat;
0123 
0124     //----------------------------------------------------------------------------------------------
0125     // Someone invited us to an event, and we accept
0126     expectedResult = ITIPHandler::ResultSuccess;
0127     data_filename = QStringLiteral("invited_us");
0128     expectedNumIncidences = 1;
0129     expectedPartStat = KCalendarCore::Attendee::Accepted;
0130     action = QStringLiteral("accepted");
0131     QTest::newRow("invited us1") << data_filename << action << receiver << incidenceUid << expectedResult << expectedNumIncidences << expectedPartStat;
0132     //----------------------------------------------------------------------------------------------
0133     // Someone invited us to an event, and we accept conditionally
0134     expectedResult = ITIPHandler::ResultSuccess;
0135     data_filename = QStringLiteral("invited_us");
0136     expectedNumIncidences = 1;
0137     expectedPartStat = KCalendarCore::Attendee::Tentative;
0138     action = QStringLiteral("tentative");
0139     QTest::newRow("invited us2") << data_filename << action << receiver << incidenceUid << expectedResult << expectedNumIncidences << expectedPartStat;
0140     //----------------------------------------------------------------------------------------------
0141     // Someone invited us to an event, we delegate it
0142     expectedResult = ITIPHandler::ResultSuccess;
0143     data_filename = QStringLiteral("invited_us");
0144 
0145     // The e-mail to the delegate is sent by kmail's text_calendar.cpp
0146     expectedNumIncidences = 1;
0147     expectedPartStat = KCalendarCore::Attendee::Delegated;
0148     action = QStringLiteral("delegated");
0149     QTest::newRow("invited us3") << data_filename << action << receiver << incidenceUid << expectedResult << expectedNumIncidences << expectedPartStat;
0150     //----------------------------------------------------------------------------------------------
0151     // Process a CANCEL without having the incidence in our calendar.
0152     // itiphandler should return success and not error
0153     expectedResult = ITIPHandler::ResultSuccess;
0154     data_filename = QStringLiteral("invited_us");
0155     expectedNumIncidences = 0;
0156     action = QStringLiteral("cancel");
0157     QTest::newRow("invited us4") << data_filename << action << receiver << incidenceUid << expectedResult << expectedNumIncidences << expectedPartStat;
0158     //----------------------------------------------------------------------------------------------
0159     // Process a REQUEST without having the incidence in our calendar.
0160     // itiphandler should return success and add the request to a calendar
0161     expectedResult = ITIPHandler::ResultSuccess;
0162     data_filename = QStringLiteral("invited_us");
0163     expectedNumIncidences = 1;
0164     expectedPartStat = KCalendarCore::Attendee::NeedsAction;
0165     action = QStringLiteral("request");
0166     QTest::newRow("invited us5") << data_filename << action << receiver << incidenceUid << expectedResult << expectedNumIncidences << expectedPartStat;
0167     //----------------------------------------------------------------------------------------------
0168     // Here we're testing an error case, where data is null.
0169     expectedResult = ITIPHandler::ResultError;
0170     expectedNumIncidences = 0;
0171     action = QStringLiteral("accepted");
0172     QTest::newRow("invalid data") << QString() << action << receiver << incidenceUid << expectedResult << expectedNumIncidences << expectedPartStat;
0173     //----------------------------------------------------------------------------------------------
0174     // Testing invalid action
0175     expectedResult = ITIPHandler::ResultError;
0176     data_filename = QStringLiteral("invitation_us");
0177     expectedNumIncidences = 0;
0178     action = QStringLiteral("accepted");
0179     QTest::newRow("invalid action") << data_filename << QString() << receiver << incidenceUid << expectedResult << expectedNumIncidences << expectedPartStat;
0180     //----------------------------------------------------------------------------------------------
0181     // Test bug 235749
0182     expectedResult = ITIPHandler::ResultSuccess;
0183     data_filename = QStringLiteral("bug235749");
0184     expectedNumIncidences = 1;
0185     expectedPartStat = KCalendarCore::Attendee::Accepted;
0186     action = QStringLiteral("accepted");
0187     incidenceUid = QStringLiteral("b6f0466a-8877-49d0-a4fc-8ee18ffd8e07"); // Don't change, hardcoded in data file
0188     QTest::newRow("bug 235749") << data_filename << action << receiver << incidenceUid << expectedResult << expectedNumIncidences << expectedPartStat;
0189     //----------------------------------------------------------------------------------------------
0190     // Test counterproposal without a UI delegat set
0191     expectedResult = ITIPHandler::ResultError;
0192     data_filename = QStringLiteral("invited_us");
0193     expectedNumIncidences = 0;
0194     expectedPartStat = KCalendarCore::Attendee::Accepted;
0195     action = QStringLiteral("counter");
0196     incidenceUid = QStringLiteral("b6f0466a-8877-49d0-a4fc-8ee18ffd8e07");
0197     QTest::newRow("counter error") << data_filename << action << receiver << incidenceUid << expectedResult << expectedNumIncidences << expectedPartStat;
0198     //----------------------------------------------------------------------------------------------
0199 }
0200 
0201 void ITIPHandlerTest::testProcessITIPMessage()
0202 {
0203     QFETCH(QString, data_filename);
0204     QFETCH(QString, action);
0205     QFETCH(QString, receiver);
0206     QFETCH(QString, incidenceUid);
0207     QFETCH(Akonadi::ITIPHandler::Result, expectedResult);
0208     QFETCH(int, expectedNumIncidences);
0209     QFETCH(KCalendarCore::Attendee::PartStat, expectedPartStat);
0210 
0211     FakeMessageQueueJob::sUnitTestResults.clear();
0212     createITIPHandler();
0213 
0214     m_expectedResult = expectedResult;
0215 
0216     QString iCalData = icalData(data_filename);
0217     Akonadi::Item::List items;
0218     processItip(iCalData, receiver, action, expectedNumIncidences, items);
0219 
0220     if (expectedNumIncidences == 1) {
0221         auto incidence = items.first().payload<KCalendarCore::Incidence::Ptr>();
0222         QVERIFY(incidence);
0223         QCOMPARE(incidence->schedulingID(), incidenceUid);
0224         QVERIFY(incidence->schedulingID() != incidence->uid());
0225 
0226         KCalendarCore::Attendee me = ourAttendee(incidence);
0227         QVERIFY(!me.isNull());
0228         QCOMPARE(me.status(), expectedPartStat);
0229     }
0230 
0231     cleanup();
0232 }
0233 
0234 void ITIPHandlerTest::testProcessITIPMessages_data()
0235 {
0236     QTest::addColumn<QStringList>("invitation_filenames"); // filename to create incidence (inputs)
0237     QTest::addColumn<QString>("expected_filename"); // filename with expected data   (reference)
0238     QTest::addColumn<QStringList>("actions"); // we must specify the METHOD. This is an ITipHandler API workaround, not sure why we must pass it as argument
0239                                               // since it's already inside the icaldata.
0240     QStringList invitation_filenames;
0241     QString expected_filename;
0242     QStringList actions;
0243     actions << QStringLiteral("accepted") << QStringLiteral("accepted");
0244 
0245     //----------------------------------------------------------------------------------------------
0246     // Someone invited us to an event, we accept, then organizer changes event, and we record update:
0247     invitation_filenames.clear();
0248     invitation_filenames << QStringLiteral("invited_us") << QStringLiteral("invited_us_update01");
0249     expected_filename = QStringLiteral("expected_data/update1");
0250     QTest::newRow("accept update") << invitation_filenames << expected_filename << actions;
0251     //----------------------------------------------------------------------------------------------
0252     // Someone invited us to an event, we accept, then organizer changes event, and we record update:
0253     invitation_filenames.clear();
0254     invitation_filenames << QStringLiteral("invited_us") << QStringLiteral("invited_us_daily_update01");
0255     expected_filename = QStringLiteral("expected_data/update2");
0256     QTest::newRow("accept recurringupdate") << invitation_filenames << expected_filename << actions;
0257     //----------------------------------------------------------------------------------------------
0258     // We accept a recurring event, then the organizer changes the summary to the second instance (RECID)
0259     expected_filename = QStringLiteral("expected_data/update3");
0260     invitation_filenames.clear();
0261     invitation_filenames << QStringLiteral("invited_us_daily") << QStringLiteral("invited_us_daily_update_recid01");
0262     QTest::newRow("accept recid update") << invitation_filenames << expected_filename << actions;
0263     //----------------------------------------------------------------------------------------------
0264     // We accept a recurring event, then we accept a CANCEL with recuring-id.
0265     // The result is that a exception with status CANCELLED should be created, and our main incidence
0266     // should not be touched
0267     invitation_filenames.clear();
0268     invitation_filenames << QStringLiteral("invited_us_daily") << QStringLiteral("invited_us_daily_cancel_recid01");
0269     expected_filename = QStringLiteral("expected_data/cancel1");
0270     actions << QStringLiteral("accepted") << QStringLiteral("cancel");
0271     QTest::newRow("accept recid cancel") << invitation_filenames << expected_filename << actions;
0272 
0273     //----------------------------------------------------------------------------------------------
0274 }
0275 
0276 void ITIPHandlerTest::testProcessITIPMessages()
0277 {
0278     QFETCH(QStringList, invitation_filenames);
0279     QFETCH(QString, expected_filename);
0280     QFETCH(QStringList, actions);
0281 
0282     const QString receiver = QLatin1StringView(s_ourEmail);
0283 
0284     FakeMessageQueueJob::sUnitTestResults.clear();
0285     createITIPHandler();
0286 
0287     m_expectedResult = Akonadi::ITIPHandler::ResultSuccess;
0288 
0289     for (int i = 0; i < invitation_filenames.count(); i++) {
0290         // First accept the invitation that creates the incidence:
0291         QString iCalData = icalData(invitation_filenames.at(i));
0292         Item::List items;
0293         qDebug() << "Processing " << invitation_filenames.at(i);
0294         processItip(iCalData, receiver, actions.at(i), -1, items);
0295     }
0296 
0297     QString expectedICalData = icalData(expected_filename);
0298     KCalendarCore::MemoryCalendar::Ptr expectedCalendar = KCalendarCore::MemoryCalendar::Ptr(new KCalendarCore::MemoryCalendar(QTimeZone::utc()));
0299     KCalendarCore::ICalFormat format;
0300     format.fromString(expectedCalendar, expectedICalData);
0301     compareCalendars(expectedCalendar); // Here's where the cool and complex comparisons are done
0302 
0303     cleanup();
0304 }
0305 
0306 void ITIPHandlerTest::testProcessITIPMessageCancel_data()
0307 {
0308     QTest::addColumn<QString>("creation_data_filename"); // filename to create incidence
0309     QTest::addColumn<QString>("cancel_data_filename"); // filename with incidence cancellation
0310     QTest::addColumn<QString>("incidenceUid"); // uid of incidence in invitation
0311 
0312     QString creation_data_filename;
0313     QString cancel_data_filename;
0314     QString incidenceUid = QStringLiteral("uosj936i6arrtl9c2i5r2mfuvg");
0315     //----------------------------------------------------------------------------------------------
0316     // Someone invited us to an event, we accept, then organizer cancels event
0317     creation_data_filename = QStringLiteral("invited_us");
0318     cancel_data_filename = QStringLiteral("invited_us_cancel01");
0319 
0320     QTest::newRow("cancel1") << creation_data_filename << cancel_data_filename << incidenceUid;
0321     //----------------------------------------------------------------------------------------------
0322     // Someone invited us to daily event, we accept, then organizer cancels the whole recurrence series
0323     creation_data_filename = QStringLiteral("invited_us_daily");
0324     cancel_data_filename = QStringLiteral("invited_us_daily_cancel01");
0325 
0326     QTest::newRow("cancel_daily") << creation_data_filename << cancel_data_filename << incidenceUid;
0327     //----------------------------------------------------------------------------------------------
0328 }
0329 
0330 void ITIPHandlerTest::testProcessITIPMessageCancel()
0331 {
0332     QFETCH(QString, creation_data_filename);
0333     QFETCH(QString, cancel_data_filename);
0334     QFETCH(QString, incidenceUid);
0335 
0336     const QString receiver = QLatin1StringView(s_ourEmail);
0337     FakeMessageQueueJob::sUnitTestResults.clear();
0338     createITIPHandler();
0339 
0340     m_expectedResult = Akonadi::ITIPHandler::ResultSuccess;
0341 
0342     // First accept the invitation that creates the incidence:
0343     QString iCalData = icalData(creation_data_filename);
0344     Item::List items;
0345     processItip(iCalData, receiver, QStringLiteral("accepted"), 1, items);
0346 
0347     auto incidence = items.first().payload<KCalendarCore::Incidence::Ptr>();
0348     QVERIFY(incidence);
0349 
0350     // good, now accept the invitation that has the CANCEL
0351     iCalData = icalData(cancel_data_filename);
0352     processItip(iCalData, receiver, QStringLiteral("accepted"), 0, items);
0353 }
0354 
0355 void ITIPHandlerTest::testOutgoingInvitations_data()
0356 {
0357     QTest::addColumn<Akonadi::Item>("item"); // existing incidence that will be target of creation, deletion or modification
0358     QTest::addColumn<Akonadi::IncidenceChanger::ChangeType>("changeType"); // creation, deletion, modification
0359     QTest::addColumn<int>("expectedEmailCount");
0360     QTest::addColumn<IncidenceChanger::InvitationPolicy>("invitationPolicy");
0361     QTest::addColumn<bool>("userCancels");
0362 
0363     const bool userDoesntCancel = false;
0364     const bool userCancels = true;
0365 
0366     Akonadi::Item item;
0367     KCalendarCore::Incidence::Ptr incidence;
0368     IncidenceChanger::ChangeType changeType;
0369     const IncidenceChanger::InvitationPolicy invitationPolicyAsk = IncidenceChanger::InvitationPolicyAsk;
0370     const IncidenceChanger::InvitationPolicy invitationPolicySend = IncidenceChanger::InvitationPolicySend;
0371     const IncidenceChanger::InvitationPolicy invitationPolicyDontSend = IncidenceChanger::InvitationPolicyDontSend;
0372     int expectedEmailCount = 0;
0373     Q_UNUSED(invitationPolicyAsk)
0374 
0375     const QString ourEmail = QLatin1StringView(s_ourEmail);
0376     Attendee us(QString(), ourEmail);
0377     const Attendee mia(QStringLiteral("Mia Wallace"), QStringLiteral("mia@dev.nul"));
0378     const Attendee vincent(QStringLiteral("Vincent"), QStringLiteral("vincent@dev.nul"));
0379     const Attendee jules(QStringLiteral("Jules"), QStringLiteral("jules@dev.nul"));
0380     const QString uid = QStringLiteral("random-uid-123");
0381 
0382     //----------------------------------------------------------------------------------------------
0383     // Creation. We are organizer. We invite another person.
0384     changeType = IncidenceChanger::ChangeTypeCreate;
0385     item = generateIncidence(uid, /**organizer=*/ourEmail);
0386     incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0387     incidence->addAttendee(vincent);
0388     incidence->addAttendee(jules);
0389     expectedEmailCount = 1;
0390     QTest::newRow("Creation. We organize.") << item << changeType << expectedEmailCount << invitationPolicySend << userDoesntCancel;
0391     //----------------------------------------------------------------------------------------------
0392     // Creation. We are organizer. We invite another person. But we choose not to send invitation e-mail.
0393     changeType = IncidenceChanger::ChangeTypeCreate;
0394     item = generateIncidence(uid, /**organizer=*/ourEmail);
0395     incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0396     incidence->addAttendee(vincent);
0397     incidence->addAttendee(jules);
0398     expectedEmailCount = 0;
0399     QTest::newRow("Creation. We organize.2") << item << changeType << expectedEmailCount << invitationPolicyDontSend << userDoesntCancel;
0400     //----------------------------------------------------------------------------------------------
0401     // We delete an event that we organized, and has attendees, that will be notified.
0402     changeType = IncidenceChanger::ChangeTypeDelete;
0403     item = generateIncidence(uid, /**organizer=*/ourEmail);
0404     incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0405     incidence->addAttendee(vincent);
0406     incidence->addAttendee(jules);
0407     expectedEmailCount = 1;
0408     QTest::newRow("Deletion. We organized.") << item << changeType << expectedEmailCount << invitationPolicySend << userDoesntCancel;
0409     //----------------------------------------------------------------------------------------------
0410     // We delete an event that we organized, and has attendees. We won't send e-mail notifications.
0411     changeType = IncidenceChanger::ChangeTypeDelete;
0412     item = generateIncidence(uid, /**organizer=*/ourEmail);
0413     incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0414     incidence->addAttendee(vincent);
0415     incidence->addAttendee(jules);
0416     expectedEmailCount = 0;
0417     QTest::newRow("Deletion. We organized.2") << item << changeType << expectedEmailCount << invitationPolicyDontSend << userDoesntCancel;
0418     //----------------------------------------------------------------------------------------------
0419     // We delete an event that we organized, and has attendees, who will be notified.
0420     changeType = IncidenceChanger::ChangeTypeModify;
0421     item = generateIncidence(uid, /**organizer=*/ourEmail);
0422     incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0423     incidence->addAttendee(vincent);
0424     incidence->addAttendee(jules);
0425     expectedEmailCount = 1;
0426     QTest::newRow("Modification. We organizd.") << item << changeType << expectedEmailCount << invitationPolicySend << userDoesntCancel;
0427     //----------------------------------------------------------------------------------------------
0428     // We delete an event that we organized, and has attendees, who won't be notified.
0429     changeType = IncidenceChanger::ChangeTypeModify;
0430     item = generateIncidence(uid, /**organizer=*/ourEmail);
0431     incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0432     incidence->addAttendee(vincent); // TODO: test that all attendees got the e-mail
0433     incidence->addAttendee(jules);
0434     expectedEmailCount = 0;
0435     QTest::newRow("Modification. We organizd.2") << item << changeType << expectedEmailCount << invitationPolicyDontSend << userDoesntCancel;
0436     //----------------------------------------------------------------------------------------------
0437     // We delete an event which we're not the organizer of. Organizer gets REPLY with PartState=Declined
0438     changeType = IncidenceChanger::ChangeTypeDelete;
0439     item = generateIncidence(uid, /**organizer=*/mia.email());
0440     incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0441     incidence->addAttendee(vincent);
0442     incidence->addAttendee(jules);
0443     us.setStatus(Attendee::Accepted); // TODO: Test without accepted status
0444     incidence->addAttendee(us); // TODO: test that attendees didn't receive the REPLY
0445     expectedEmailCount = 1; // REPLY is always sent, there are no dialogs to control this. Dialogs only control REQUEST and CANCEL. Bug or feature ?
0446     QTest::newRow("Deletion. We didn't organize.") << item << changeType << expectedEmailCount << invitationPolicyDontSend << userDoesntCancel;
0447     //----------------------------------------------------------------------------------------------
0448     // We delete an event which we're not the organizer of. Organizer gets REPLY with PartState=Declined
0449     changeType = IncidenceChanger::ChangeTypeDelete;
0450     item = generateIncidence(uid, /**organizer=*/mia.email());
0451     incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0452     incidence->addAttendee(vincent);
0453     incidence->addAttendee(jules); // TODO: test that attendees didn't receive the REPLY
0454     us.setStatus(Attendee::Accepted); // TODO: Test without accepted status
0455     incidence->addAttendee(us);
0456     expectedEmailCount = 1;
0457     QTest::newRow("Deletion. We didn't organize.2") << item << changeType << expectedEmailCount << invitationPolicySend << userDoesntCancel;
0458     //----------------------------------------------------------------------------------------------
0459     // We modified an event which we're not the organizer of. And, when the "do you really want to modify", we choose "yes".
0460     changeType = IncidenceChanger::ChangeTypeModify;
0461     item = generateIncidence(uid, /**organizer=*/mia.email());
0462     incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0463     incidence->addAttendee(vincent);
0464     incidence->addAttendee(jules);
0465     us.setStatus(Attendee::Accepted);
0466     incidence->addAttendee(us);
0467     expectedEmailCount = 0;
0468     QTest::newRow("Modification. We didn't organize") << item << changeType << expectedEmailCount << invitationPolicySend << userDoesntCancel;
0469     //----------------------------------------------------------------------------------------------
0470     // We modified an event which we're not the organizer of. And, when the "do you really want to modify", we choose "no".
0471     changeType = IncidenceChanger::ChangeTypeModify;
0472     item = generateIncidence(uid, /**organizer=*/mia.email());
0473     incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0474     incidence->addAttendee(vincent);
0475     incidence->addAttendee(jules);
0476     us.setStatus(Attendee::Accepted);
0477     incidence->addAttendee(us);
0478     expectedEmailCount = 0;
0479     QTest::newRow("Modification. We didn't organize.2") << item << changeType << expectedEmailCount << invitationPolicyDontSend << userCancels;
0480     //----------------------------------------------------------------------------------------------
0481 }
0482 
0483 void ITIPHandlerTest::testOutgoingInvitations()
0484 {
0485     QFETCH(Akonadi::Item, item);
0486     QFETCH(IncidenceChanger::ChangeType, changeType);
0487     QFETCH(int, expectedEmailCount);
0488     QFETCH(IncidenceChanger::InvitationPolicy, invitationPolicy);
0489     QFETCH(bool, userCancels);
0490     auto incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0491 
0492     m_pendingIncidenceChangerSignal = 1;
0493     FakeMessageQueueJob::sUnitTestResults.clear();
0494     m_changer->setInvitationPolicy(invitationPolicy);
0495 
0496     m_cancelExpected = userCancels;
0497 
0498     switch (changeType) {
0499     case IncidenceChanger::ChangeTypeCreate:
0500         m_changer->createIncidence(incidence, mCollection);
0501         waitForIt();
0502         QCOMPARE(FakeMessageQueueJob::sUnitTestResults.count(), expectedEmailCount);
0503         break;
0504     case IncidenceChanger::ChangeTypeModify: {
0505         // Create if first, so we have something to modify
0506         m_changer->setGroupwareCommunication(false); // we disable groupware because creating an incidence which we're not the organizer of is not permitted.
0507         m_changer->createIncidence(incidence, mCollection);
0508         waitForIt();
0509         m_changer->setGroupwareCommunication(true);
0510         QCOMPARE(FakeMessageQueueJob::sUnitTestResults.count(), 0);
0511         QVERIFY(mLastInsertedItem.isValid());
0512 
0513         m_pendingIncidenceChangerSignal = 1;
0514         Incidence::Ptr oldIncidence = Incidence::Ptr(incidence->clone());
0515         incidence->setSummary(QStringLiteral("the-new-summary"));
0516         int changeId = m_changer->modifyIncidence(mLastInsertedItem, oldIncidence);
0517         QVERIFY(changeId != 1);
0518         waitForIt();
0519         QCOMPARE(FakeMessageQueueJob::sUnitTestResults.count(), expectedEmailCount);
0520         break;
0521     }
0522     case IncidenceChanger::ChangeTypeDelete:
0523         // Create if first, so we have something to delete
0524         m_changer->setGroupwareCommunication(false);
0525         m_changer->createIncidence(incidence, mCollection);
0526         waitForIt();
0527         m_changer->setGroupwareCommunication(true);
0528         QCOMPARE(FakeMessageQueueJob::sUnitTestResults.count(), 0);
0529 
0530         QVERIFY(mLastInsertedItem.isValid());
0531         m_pendingIncidenceChangerSignal = 1;
0532         m_changer->deleteIncidence(mLastInsertedItem);
0533         waitForIt();
0534         QCOMPARE(FakeMessageQueueJob::sUnitTestResults.count(), expectedEmailCount);
0535         break;
0536     default:
0537         Q_ASSERT(false);
0538     }
0539 }
0540 
0541 void ITIPHandlerTest::testIdentity_data()
0542 {
0543     QTest::addColumn<QString>("email");
0544     QTest::addColumn<bool>("expectedResult");
0545 
0546     const QString myEmail = QLatin1StringView(s_ourEmail);
0547     QString myEmail2 = QStringLiteral("Some name <%1>").arg(myEmail);
0548 
0549     const QString myAlias1 = QStringLiteral("alias1@kde.org"); // hardcoded in emailidentities, do not change
0550     const QString myIdentity2 = QLatin1StringView(s_outEmail2);
0551 
0552     QTest::newRow("Me") << myEmail << true;
0553     QTest::newRow("Also me") << myEmail2 << true;
0554     QTest::newRow("My identity2") << myIdentity2 << true;
0555     QTest::newRow("Not me") << QStringLiteral("laura.palmer@twinpeaks.com") << false;
0556 
0557     QTest::newRow("My alias") << myAlias1 << true;
0558 }
0559 
0560 void ITIPHandlerTest::testIdentity()
0561 {
0562     QFETCH(QString, email);
0563     QFETCH(bool, expectedResult);
0564 
0565     if (CalendarUtils::thatIsMe(email) != expectedResult) {
0566         qDebug() << email;
0567         QVERIFY(false);
0568     }
0569 }
0570 
0571 void ITIPHandlerTest::cleanup()
0572 {
0573     const Akonadi::Item::List items = calendarItems();
0574     for (const Akonadi::Item &item : items) {
0575         auto job = new ItemDeleteJob(item);
0576         AKVERIFYEXEC(job);
0577     }
0578 
0579     delete m_itipHandler;
0580     m_itipHandler = nullptr;
0581 }
0582 
0583 void ITIPHandlerTest::createITIPHandler()
0584 {
0585     m_itipHandler = new Akonadi::ITIPHandler(new FakeITIPHandlerComponentFactory(this), this);
0586     m_itipHandler->setShowDialogsOnError(false);
0587     connect(m_itipHandler, &ITIPHandler::iTipMessageProcessed, this, &ITIPHandlerTest::oniTipMessageProcessed);
0588 }
0589 
0590 QString ITIPHandlerTest::icalData(const QString &data_filename)
0591 {
0592     QString absolutePath = QFINDTESTDATA(QLatin1StringView("itip_data/") + data_filename);
0593     return QString::fromLatin1(readFile(absolutePath));
0594 }
0595 
0596 void ITIPHandlerTest::processItip(const QString &icaldata,
0597                                   const QString &receiver,
0598                                   const QString &action,
0599                                   int expectedNumIncidences,
0600                                   Akonadi::Item::List &items)
0601 {
0602     items.clear();
0603     m_pendingItipMessageSignal = 1;
0604     m_itipHandler->processiTIPMessage(receiver, icaldata, action);
0605     waitForIt();
0606 
0607     // 0 e-mails are sent because the status update e-mail is sent by
0608     // kmail's text_calendar.cpp.
0609     QCOMPARE(FakeMessageQueueJob::sUnitTestResults.count(), 0);
0610 
0611     items = calendarItems();
0612 
0613     if (expectedNumIncidences != -1) {
0614         QCOMPARE(items.count(), expectedNumIncidences);
0615     }
0616 }
0617 
0618 Attendee ITIPHandlerTest::ourAttendee(const KCalendarCore::Incidence::Ptr &incidence) const
0619 {
0620     const KCalendarCore::Attendee::List attendees = incidence->attendees();
0621     KCalendarCore::Attendee me;
0622     for (const KCalendarCore::Attendee &attendee : attendees) {
0623         if (attendee.email() == QLatin1StringView(s_ourEmail)) {
0624             me = attendee;
0625             break;
0626         }
0627     }
0628 
0629     return me;
0630 }
0631 
0632 void ITIPHandlerTest::oniTipMessageProcessed(ITIPHandler::Result result, const QString &errorMessage)
0633 {
0634     if (result != ITIPHandler::ResultSuccess && result != m_expectedResult) {
0635         qDebug() << "ITIPHandlerTest::oniTipMessageProcessed() error = " << errorMessage;
0636     }
0637 
0638     m_pendingItipMessageSignal--;
0639     QVERIFY(m_pendingItipMessageSignal >= 0);
0640     if (m_pendingItipMessageSignal == 0) {
0641         stopWaiting();
0642     }
0643 
0644     QCOMPARE(m_expectedResult, result);
0645 }
0646 
0647 void ITIPHandlerTest::onCreateFinished(int changeId, const Item &item, IncidenceChanger::ResultCode resultCode, const QString &errorString)
0648 {
0649     Q_UNUSED(changeId)
0650     Q_UNUSED(errorString)
0651     mLastInsertedItem = item;
0652     m_pendingIncidenceChangerSignal--;
0653     QVERIFY(m_pendingIncidenceChangerSignal >= 0);
0654     if (m_pendingIncidenceChangerSignal == 0) {
0655         stopWaiting();
0656     }
0657     QCOMPARE(resultCode, IncidenceChanger::ResultCodeSuccess);
0658 }
0659 
0660 void ITIPHandlerTest::onDeleteFinished(int changeId,
0661                                        const QList<Akonadi::Item::Id> &deletedIds,
0662                                        IncidenceChanger::ResultCode resultCode,
0663                                        const QString &errorString)
0664 {
0665     Q_UNUSED(changeId)
0666     Q_UNUSED(errorString)
0667     Q_UNUSED(deletedIds)
0668 
0669     m_pendingIncidenceChangerSignal--;
0670     QVERIFY(m_pendingIncidenceChangerSignal >= 0);
0671     if (m_pendingIncidenceChangerSignal == 0) {
0672         stopWaiting();
0673     }
0674     QCOMPARE(resultCode, IncidenceChanger::ResultCodeSuccess);
0675 }
0676 
0677 void ITIPHandlerTest::onModifyFinished(int changeId, const Item &item, IncidenceChanger::ResultCode resultCode, const QString &errorString)
0678 {
0679     Q_UNUSED(changeId)
0680     Q_UNUSED(errorString)
0681     Q_UNUSED(item)
0682 
0683     m_pendingIncidenceChangerSignal--;
0684     QVERIFY(m_pendingIncidenceChangerSignal >= 0);
0685     if (m_pendingIncidenceChangerSignal == 0) {
0686         stopWaiting();
0687     }
0688     QCOMPARE(resultCode, m_cancelExpected ? IncidenceChanger::ResultCodeUserCanceled : IncidenceChanger::ResultCodeSuccess);
0689 }
0690 
0691 QTEST_AKONADIMAIN(ITIPHandlerTest)
0692 
0693 #include "moc_itiphandlertest.cpp"