diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-24 16:31:25 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-24 16:31:25 +0200 |
| commit | 27f9e90b26622beb47d4f00e7f21a56b365ccfaf (patch) | |
| tree | 543af8caa246fc16449a4cf47e6ba43990e3f960 | |
| parent | 2c3741619a3e0f11911af7100e3f763ded9077cf (diff) | |
| download | qtmaildir-27f9e90b26622beb47d4f00e7f21a56b365ccfaf.tar.gz qtmaildir-27f9e90b26622beb47d4f00e7f21a56b365ccfaf.zip | |
fix: compare sameMeaning overrides by recurrence id and check the uid
| -rw-r--r-- | src/calendarstore.cpp | 14 | ||||
| -rw-r--r-- | tests/test_calendarstore.cpp | 38 |
2 files changed, 50 insertions, 2 deletions
diff --git a/src/calendarstore.cpp b/src/calendarstore.cpp index 92a7b74..c388cd6 100644 --- a/src/calendarstore.cpp +++ b/src/calendarstore.cpp @@ -604,10 +604,20 @@ bool sameMeaning(const QByteArray &a, const QByteArray &b) const CalEvent y = parseEvent(b, {}, {}, &ignored); if (x.uid.isEmpty() || y.uid.isEmpty()) return x.uid.isEmpty() && y.uid.isEmpty(); + if (x.uid != y.uid) + return false; if (x.overrides.size() != y.overrides.size()) return false; - for (int i = 0; i < x.overrides.size(); ++i) { - const CalOverride &p = x.overrides[i], &q = y.overrides[i]; + // Overrides are separate VEVENTs whose file order is not semantic, and a + // server may normalise it, so pair them by recurrence id, not by index. + QList<CalOverride> xo = x.overrides, yo = y.overrides; + const auto byRecurrenceId = [](const CalOverride &a, const CalOverride &b) { + return a.recurrenceId < b.recurrenceId; + }; + std::stable_sort(xo.begin(), xo.end(), byRecurrenceId); + std::stable_sort(yo.begin(), yo.end(), byRecurrenceId); + for (int i = 0; i < xo.size(); ++i) { + const CalOverride &p = xo[i], &q = yo[i]; if (p.recurrenceId != q.recurrenceId || p.start != q.start || p.end != q.end || p.summary != q.summary || p.cancelled != q.cancelled) return false; diff --git a/tests/test_calendarstore.cpp b/tests/test_calendarstore.cpp index 977f96b..ba2d5c7 100644 --- a/tests/test_calendarstore.cpp +++ b/tests/test_calendarstore.cpp @@ -139,6 +139,8 @@ private slots: void anEditCanTurnAnEventAllDayAndBack(); void aNewEventIsCompleteAndEmbedsItsZone(); void sameMeaningIgnoresFormatting(); + void sameMeaningMatchesOverridesRegardlessOfOrder(); + void sameMeaningRejectsDifferentUids(); }; void TestCalendarStore::parsesAUtcEvent() @@ -549,5 +551,41 @@ void TestCalendarStore::sameMeaningIgnoresFormatting() QVERIFY(!CalendarStore::sameMeaning(a, moved)); } +void TestCalendarStore::sameMeaningMatchesOverridesRegardlessOfOrder() +{ + // Two SERVER-normalised texts: the same master and the same two overrides, + // but the overrides are separate VEVENTs and their file order is not + // semantic, so a reordered pair still describes one event. + const QString master = vevent(QStringLiteral( + "UID:ord@example.org\r\nDTSTART:20260921T080000Z\r\nDTEND:20260921T090000Z\r\n" + "RRULE:FREQ=DAILY;COUNT=5\r\nSUMMARY:Series\r\n")); + const QString first = vevent(QStringLiteral( + "UID:ord@example.org\r\nRECURRENCE-ID:20260922T080000Z\r\n" + "DTSTART:20260922T080000Z\r\nDTEND:20260922T090000Z\r\nSUMMARY:Moved A\r\n")); + const QString second = vevent(QStringLiteral( + "UID:ord@example.org\r\nRECURRENCE-ID:20260923T080000Z\r\n" + "DTSTART:20260923T100000Z\r\nDTEND:20260923T110000Z\r\nSUMMARY:Moved B\r\n")); + const QByteArray a = ics(master + first + second); + const QByteArray b = ics(master + second + first); + QVERIFY(CalendarStore::sameMeaning(a, b)); + + // The order is only ignored, not the contents. + const QString changed = vevent(QStringLiteral( + "UID:ord@example.org\r\nRECURRENCE-ID:20260923T080000Z\r\n" + "DTSTART:20260923T100000Z\r\nDTEND:20260923T110000Z\r\nSUMMARY:Moved C\r\n")); + QVERIFY(!CalendarStore::sameMeaning(a, ics(master + second + changed))); +} + +void TestCalendarStore::sameMeaningRejectsDifferentUids() +{ + // Identical fields, different events: the UID is the identity. + const QByteArray a = ics(vevent(QStringLiteral( + "UID:one@example.org\r\nDTSTART:20260922T080000Z\r\nSUMMARY:Standup\r\n"))); + const QByteArray b = ics(vevent(QStringLiteral( + "UID:two@example.org\r\nDTSTART:20260922T080000Z\r\nSUMMARY:Standup\r\n"))); + QVERIFY(!CalendarStore::sameMeaning(a, b)); + QVERIFY(CalendarStore::sameMeaning(a, a)); +} + QTEST_MAIN(TestCalendarStore) #include "test_calendarstore.moc" |
