Index: akonadi/calendar/CMakeLists.txt =================================================================== --- akonadi/calendar/CMakeLists.txt +++ akonadi/calendar/CMakeLists.txt @@ -6,7 +6,7 @@ # TODO: Add a cmake option for this and enable it on jenkins -set( PLEASE_TEST_INVITATIONS FALSE) +set( PLEASE_TEST_INVITATIONS TRUE) if ( PLEASE_TEST_INVITATIONS ) add_definitions( -DPLEASE_TEST_INVITATIONS ) endif() Index: akonadi/calendar/incidencechanger.cpp =================================================================== --- akonadi/calendar/incidencechanger.cpp +++ akonadi/calendar/incidencechanger.cpp @@ -73,10 +73,11 @@ { // Modifications that are per user allowd without getting outofsync with organisator // * if only alarm settings are modified. + // * if we add/modify the SchedulingId const QSet dirtyFields = incidence->dirtyFields(); - QSet alarmOnlyModify; - alarmOnlyModify << IncidenceBase::FieldAlarms << IncidenceBase::FieldLastModified; - if (dirtyFields == alarmOnlyModify) { + QSet allowedModifications; + allowedModifications << IncidenceBase::FieldAlarms << IncidenceBase::FieldLastModified << IncidenceBase::FieldSchedulingId; + if ((dirtyFields - allowedModifications).isEmpty()) { return true; } @@ -668,7 +669,6 @@ newMe->setStatus(KCalCore::Attendee::Declined); incidence->clearAttendees(); incidence->addAttendee(newMe); - break; } if (notifyOrganizer) { Index: akonadi/calendar/scheduler_p.h =================================================================== --- akonadi/calendar/scheduler_p.h +++ akonadi/calendar/scheduler_p.h @@ -179,6 +179,7 @@ private: void connectCalendar(const Akonadi::CalendarBase::Ptr &calendar); + KCalCore::Incidence::List incidences(const Akonadi::CalendarBase::Ptr &calendar, const QString &schedulingUid, const QString &instanceIdentifier); Q_DISABLE_COPY(Scheduler) struct Private; Private *const d; Index: akonadi/calendar/scheduler_p.cpp =================================================================== --- akonadi/calendar/scheduler_p.cpp +++ akonadi/calendar/scheduler_p.cpp @@ -171,6 +171,20 @@ emit transactionFinished(result, errorString); } +Incidence::List Scheduler::incidences(const Akonadi::CalendarBase::Ptr &calendar, const QString &schedulingUid, const QString &instanceIdentifier) +{ + Incidence::List result; + const Incidence::List incidences = calendar->rawIncidences(); + Incidence::List::const_iterator it = incidences.begin(); + for (; it != incidences.end(); ++it) { + if ( ((*it)->instanceIdentifier() == instanceIdentifier) + || ((*it)->schedulingID() == schedulingUid)) { + result.append(*it); + } + } + return result; +} + void Scheduler::acceptRequest(const IncidenceBase::Ptr &incidenceBase, const Akonadi::CalendarBase::Ptr &calendar, ScheduleMessage::Status status, @@ -188,7 +202,7 @@ QString errorString; Result result = ResultSuccess; - const Incidence::List existingIncidences = calendar->incidencesFromSchedulingID(schedulingUid); + const Incidence::List existingIncidences = incidences(calendar, schedulingUid, incidence->instanceIdentifier()); kDebug() << "status=" << KCalUtils::Stringify::scheduleMessageStatus(status) //krazy:exclude=kdebug << ": found " << existingIncidences.count() << " incidences with schedulingID " << incidence->schedulingID() @@ -225,6 +239,7 @@ // This is supposed to be a new request, not an update - however we want to update // the existing one to handle the "clicking more than once on the invitation" case. // So check the attendee status of the attendee. + /* const Attendee::List attendees = existingIncidence->attendees(); Attendee::List::ConstIterator ait; for (ait = attendees.begin(); ait != attendees.end(); ++ait) { @@ -235,7 +250,7 @@ isUpdate = false; break; } - } + }*/ if (isUpdate) { if (existingRevision == incidence->revision() && existingIncidence->lastModified() > incidence->lastModified()) { @@ -348,7 +363,7 @@ return; } - const Incidence::List existingIncidences = calendar->incidencesFromSchedulingID(incidence->uid()); + const Incidence::List existingIncidences = incidences(calendar, incidence->uid(), incidence->instanceIdentifier()); kDebug() << "Scheduler::acceptCancel=" << KCalUtils::Stringify::scheduleMessageStatus(status) //krazy2:exclude=kdebug << ": found " << existingIncidences.count() @@ -368,6 +383,7 @@ const QString existingUid = existingIncidence->uid(); + /* // Code for new invitations: // We cannot check the value of "status" to be RequestNew because // "status" comes from a similar check inside libical, where the event @@ -394,7 +410,7 @@ if (!isMine) { continue; - } + }*/ kDebug() << "removing existing incidence " << existingUid; if (incidence->hasRecurrenceId()) { Index: akonadi/calendar/tests/itiphandlertest.h =================================================================== --- akonadi/calendar/tests/itiphandlertest.h +++ akonadi/calendar/tests/itiphandlertest.h @@ -40,10 +40,17 @@ void testProcessITIPMessages_data(); void testProcessITIPMessages(); + void testProcessITIPMessagesUpdate_data(); + void testProcessITIPMessagesUpdate(); + // Deprecated methods, use testProcessITIPMessages() for new stuff void testProcessITIPMessage_data(); void testProcessITIPMessage(); + //Test if an existing event is updated + void testProcessITIPMessageUpdate_data(); + void testProcessITIPMessageUpdate(); + // Deprecated methods do test CANCEL. void testProcessITIPMessageCancel_data(); void testProcessITIPMessageCancel(); Index: akonadi/calendar/tests/itiphandlertest.cpp =================================================================== --- akonadi/calendar/tests/itiphandlertest.cpp +++ akonadi/calendar/tests/itiphandlertest.cpp @@ -117,6 +117,116 @@ SLOT(onModifyFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString))); } +void ITIPHandlerTest::testProcessITIPMessageUpdate_data() +{ + QTest::addColumn("data_filename"); + QTest::addColumn("action"); + QTest::addColumn("receiver"); + QTest::addColumn("incidenceUid"); // uid of incidence in invitation + QTest::addColumn("expectedResult"); + QTest::addColumn("expectedNumIncidences"); + QTest::addColumn("expectedPartStat"); + + QString data_filename; + QString action = QLatin1String("accepted"); + QString incidenceUid = QString::fromLatin1("uosj936i6arrtl9c2i5r2mfuvg"); + QString receiver = QLatin1String(s_ourEmail); + Akonadi::ITIPHandler::Result expectedResult; + int expectedNumIncidences = 0; + KCalCore::Attendee::PartStat expectedPartStat; + + //---------------------------------------------------------------------------------------------- + // Someone invited us to an event, and we accept + expectedResult = ITIPHandler::ResultSuccess; + data_filename = QLatin1String("invited_us"); + expectedNumIncidences = 1; + expectedPartStat = KCalCore::Attendee::Accepted; + action = QLatin1String("accepted"); + QTest::newRow("invited us1") << data_filename << action << receiver << incidenceUid + << expectedResult + << expectedNumIncidences + << expectedPartStat; + //---------------------------------------------------------------------------------------------- + // Someone invited us to an event, and we accept conditionally + expectedResult = ITIPHandler::ResultSuccess; + data_filename = QLatin1String("invited_us"); + expectedNumIncidences = 1; + expectedPartStat = KCalCore::Attendee::Tentative; + action = QLatin1String("tentative"); + QTest::newRow("invited us2") << data_filename << action << receiver << incidenceUid + << expectedResult + << expectedNumIncidences + << expectedPartStat; + //---------------------------------------------------------------------------------------------- + // Someone invited us to an event, we delegate it + expectedResult = ITIPHandler::ResultSuccess; + data_filename = QLatin1String("invited_us"); + + // The e-mail to the delegate is sent by kmail's text_calendar.cpp + expectedNumIncidences = 1; + expectedPartStat = KCalCore::Attendee::Delegated; + action = QLatin1String("delegated"); + QTest::newRow("invited us3") << data_filename << action << receiver << incidenceUid + << expectedResult + << expectedNumIncidences + << expectedPartStat; + //---------------------------------------------------------------------------------------------- + // Process a CANCEL without having the incidence in our calendar. + // itiphandler should return success and not error + expectedResult = ITIPHandler::ResultSuccess; + data_filename = QLatin1String("invited_us"); + expectedNumIncidences = 0; + action = QLatin1String("cancel"); + QTest::newRow("invited us4") << data_filename << action << receiver << incidenceUid + << expectedResult + << expectedNumIncidences + << expectedPartStat; + //---------------------------------------------------------------------------------------------- + // Process a REQUEST without having the incidence in our calendar. + // itiphandler should return success and add the rquest to a calendar + expectedResult = ITIPHandler::ResultSuccess; + data_filename = QLatin1String("invited_us"); + expectedNumIncidences = 1; + expectedPartStat = KCalCore::Attendee::NeedsAction; + action = QLatin1String("request"); + QTest::newRow("invited us5") << data_filename << action << receiver << incidenceUid + << expectedResult + << expectedNumIncidences + << expectedPartStat; + //---------------------------------------------------------------------------------------------- + // Here we're testing an error case, where data is null. + expectedResult = ITIPHandler::ResultError; + expectedNumIncidences = 0; + action = QLatin1String("accepted"); + QTest::newRow("invalid data") << QString() << action << receiver << incidenceUid + << expectedResult + << expectedNumIncidences + << expectedPartStat; + //---------------------------------------------------------------------------------------------- + // Testing invalid action + expectedResult = ITIPHandler::ResultError; + data_filename = QLatin1String("invitation_us"); + expectedNumIncidences = 0; + action = QLatin1String("accepted"); + QTest::newRow("invalid action") << data_filename << QString() << receiver << incidenceUid + << expectedResult + << expectedNumIncidences + << expectedPartStat; + //---------------------------------------------------------------------------------------------- + // Test bug 235749 + expectedResult = ITIPHandler::ResultSuccess; + data_filename = QLatin1String("bug235749"); + expectedNumIncidences = 1; + expectedPartStat = KCalCore::Attendee::Accepted; + action = QLatin1String("accepted"); + incidenceUid = QLatin1String("b6f0466a-8877-49d0-a4fc-8ee18ffd8e07"); // Don't change, hardcoded in data file + QTest::newRow("bug 235749") << data_filename << action << receiver << incidenceUid + << expectedResult + << expectedNumIncidences + << expectedPartStat; + //---------------------------------------------------------------------------------------------- +} + void ITIPHandlerTest::testProcessITIPMessage_data() { QTest::addColumn("data_filename"); @@ -254,15 +364,59 @@ m_expectedResult = expectedResult; + processItip(iCalData, receiver, action, expectedNumIncidences, items); + + if (expectedNumIncidences == 1) { + KCalCore::Incidence::Ptr incidence = items.first().payload(); + QVERIFY(incidence); + QCOMPARE(incidence->schedulingID(), incidenceUid); + QVERIFY(incidence->schedulingID() != incidence->uid()); + + KCalCore::Attendee::Ptr me = ourAttendee(incidence); + QVERIFY(me); + QCOMPARE(me->status(), expectedPartStat); + } + + cleanup(); +} + +void ITIPHandlerTest::testProcessITIPMessageUpdate() +{ + QFETCH(QString, data_filename); + QFETCH(QString, action); + QFETCH(QString, receiver); + QFETCH(QString, incidenceUid); + QFETCH(Akonadi::ITIPHandler::Result, expectedResult); + QFETCH(int, expectedNumIncidences); + QFETCH(KCalCore::Attendee::PartStat, expectedPartStat); + + FakeMessageQueueJob::sUnitTestResults.clear(); + createITIPHandler(); + + m_expectedResult = expectedResult; + QString iCalData = icalData(data_filename); Akonadi::Item::List items; + KCalCore::MemoryCalendar::Ptr expectedCalendar = KCalCore::MemoryCalendar::Ptr(new KCalCore::MemoryCalendar(KDateTime::UTC)); + KCalCore::ICalFormat format; + format.fromString(expectedCalendar, iCalData); + + if (!iCalData.isEmpty()) { //Create starting data as akonadi item to test update + Akonadi::Item item; + const KCalCore::Incidence::Ptr inc = expectedCalendar->incidences().first(); + item.setMimeType(KCalCore::Event::eventMimeType()); + item.setPayload(inc); + createIncidence(item); + QCOMPARE(calendarItems().count(), 1); + } processItip(iCalData, receiver, action, expectedNumIncidences, items); if (expectedNumIncidences == 1) { KCalCore::Incidence::Ptr incidence = items.first().payload(); + cleanup(); QVERIFY(incidence); QCOMPARE(incidence->schedulingID(), incidenceUid); - QVERIFY(incidence->schedulingID() != incidence->uid()); + QCOMPARE(incidence->schedulingID(), incidence->uid()); KCalCore::Attendee::Ptr me = ourAttendee(incidence); QVERIFY(me); @@ -313,6 +467,11 @@ //---------------------------------------------------------------------------------------------- } +void ITIPHandlerTest::testProcessITIPMessagesUpdate_data() +{ + testProcessITIPMessages_data(); +} + void ITIPHandlerTest::testProcessITIPMessages() { QFETCH(QStringList, invitation_filenames); @@ -344,6 +503,51 @@ cleanup(); } +void ITIPHandlerTest::testProcessITIPMessagesUpdate() +{ + QFETCH(QStringList, invitation_filenames); + QFETCH(QString, expected_filename); + QFETCH(QStringList, actions); + + const QString receiver = QLatin1String(s_ourEmail); + + FakeMessageQueueJob::sUnitTestResults.clear(); + createITIPHandler(); + + m_expectedResult = Akonadi::ITIPHandler::ResultSuccess; + + { //create first invitation as akonadi item, to test if the update process is working + const QString iCalData = icalData(invitation_filenames.first()); + KCalCore::MemoryCalendar::Ptr expectedCalendar = KCalCore::MemoryCalendar::Ptr(new KCalCore::MemoryCalendar(KDateTime::UTC)); + KCalCore::ICalFormat format; + format.fromString(expectedCalendar, iCalData); + + Akonadi::Item item; + const KCalCore::Incidence::Ptr inc = expectedCalendar->incidences().first(); + item.setMimeType(KCalCore::Event::eventMimeType()); + item.setPayload(inc); + createIncidence(item); + QCOMPARE(calendarItems().count(), 1); + } + + for (int i=0; i("creation_data_filename"); // filename to create incidence @@ -573,6 +777,8 @@ QCOMPARE(FakeMessageQueueJob::sUnitTestResults.count(), 0); QVERIFY(mLastInsertedItem.isValid()); + QCOMPARE(calendarItems().count(), 1); + m_pendingIncidenceChangerSignal = 1; m_changer->deleteIncidence(mLastInsertedItem); waitForIt(); @@ -581,7 +787,7 @@ default: Q_ASSERT(false); } - + cleanup(); } void ITIPHandlerTest::testIdentity_data() Index: kcalcore/incidence.cpp =================================================================== --- kcalcore/incidence.cpp +++ kcalcore/incidence.cpp @@ -965,11 +965,13 @@ void Incidence::setSchedulingID(const QString &sid, const QString &uid) { - d->mSchedulingID = sid; if (!uid.isEmpty()) { setUid(uid); } - setFieldDirty(FieldSchedulingId); + if (sid != d->mSchedulingID) { + d->mSchedulingID = sid; + setFieldDirty(FieldSchedulingId); + } } QString Incidence::schedulingID() const Index: kcalcore/incidencebase.cpp =================================================================== --- kcalcore/incidencebase.cpp +++ kcalcore/incidencebase.cpp @@ -226,10 +226,12 @@ void IncidenceBase::setUid(const QString &uid) { - update(); - d->mUid = uid; - d->mDirtyFields.insert(FieldUid); - updated(); + if (d->mUid != uid) { + update(); + d->mUid = uid; + d->mDirtyFields.insert(FieldUid); + updated(); + } } QString IncidenceBase::uid() const