1 /*
2 * SPDX-FileCopyrightText: 2012 Christian Mollekopf <mollekopf@kolabsys.com>
3 *
4 * SPDX-License-Identifier: LGPL-3.0-or-later
5 */
6
7 #ifndef TESTUTILS_H
8 #define TESTUTILS_H
9
10 #include <kolabevent.h>
11
12 #include "kolabformat/kolabobject.h"
13
14 #include <KMime/Message>
15
16 #include <QFile>
17 #include <QRegularExpression>
18 #include <QUuid>
19
20 Q_DECLARE_METATYPE(Kolab::ObjectType)
21 Q_DECLARE_METATYPE(Kolab::Version)
22
23 #define KCOMPARE(actual, expected) \
24 do { \
25 if (!(actual == expected)) { \
26 qDebug() << __FILE__ << ':' << __LINE__ << "Actual: " #actual ": " << actual << "\nExpected: " #expected ": " << expected; \
27 return false; \
28 } \
29 } while (0)
30
31 #endif
32
33 #define DIFFCOMPARE(actual, expected) \
34 do { \
35 if (!(actual.simplified() == expected.simplified())) { \
36 qDebug() << "Content not the same."; \
37 qDebug() << "actual." << actual.simplified() << "\n"; \
38 qDebug() << "expected." << expected.simplified(); \
39 showDiff(expected, actual); \
40 QTest::qFail("Compared versions differ.", __FILE__, __LINE__); \
41 return; \
42 } \
43 } while (0)
44
45 #define TESTVALUE(type, name) *static_cast<type *>(QTest::qData(#name, ::qMetaTypeId<type>()))
46
47 const QString TESTFILEDIR = QString::fromLatin1(TEST_DATA_PATH "/testfiles/");
48
getPath(const char * file)49 QString getPath(const char *file)
50 {
51 return TESTFILEDIR + QString::fromLatin1(file);
52 }
53
showDiff(const QString & expected,const QString & converted)54 void showDiff(const QString &expected, const QString &converted)
55 {
56 if (expected.isEmpty() || converted.isEmpty()) {
57 qWarning() << "files are empty";
58 return;
59 }
60 if (expected == converted) {
61 qWarning() << "contents are the same";
62 return;
63 }
64
65 qDebug() << "EXPECTED: " << expected;
66 qDebug() << "CONVERTED: " << converted;
67 }
68
readMimeFile(const QString & fileName,bool & ok)69 KMime::Message::Ptr readMimeFile(const QString &fileName, bool &ok)
70 {
71 // qDebug() << fileName;
72 QFile file(fileName);
73 ok = file.open(QFile::ReadOnly);
74 if (!ok) {
75 qWarning() << "failed to open file: " << fileName;
76 return KMime::Message::Ptr();
77 }
78 const QByteArray data = file.readAll();
79
80 KMime::Message::Ptr msg = KMime::Message::Ptr(new KMime::Message);
81 msg->setContent(data);
82 msg->parse();
83
84 return msg;
85 }
86
normalizeMimemessage(QString & content)87 void normalizeMimemessage(QString &content)
88 {
89 content.replace(QRegularExpression(QStringLiteral("\\bLibkolab-\\d.\\d.\\d\\b")), QStringLiteral("Libkolab-x.x.x"));
90 content.replace(QRegularExpression(QStringLiteral("\\bLibkolabxml-\\d.\\d.\\d\\b")), QStringLiteral("Libkolabxml-x.x.x"));
91 content.replace(QRegularExpression(QStringLiteral("\\bLibkolab-\\d.\\d\\b")), QStringLiteral("Libkolab-x.x.x"));
92 content.replace(QRegularExpression(QStringLiteral("\\bkdepim-runtime-\\d.\\d\\b")), QStringLiteral("Libkolab-x.x.x"));
93 content.replace(QRegularExpression(QStringLiteral("\\bLibkolabxml-\\d.\\d\\b")), QStringLiteral("Libkolabxml-x.x.x"));
94
95 content.replace(QRegularExpression(QStringLiteral("<uri>cid:.*@kolab.resource.akonadi</uri>")), QStringLiteral("<uri>cid:id@kolab.resource.akonadi</uri>"));
96
97 content.replace(QRegularExpression(QStringLiteral("Content-ID: <.*@kolab.resource.akonadi>")), QStringLiteral("Content-ID: <id@kolab.resource.akonadi>"));
98
99 content.replace(QRegularExpression(QStringLiteral("<uri>mailto:.*</uri>")), QStringLiteral("<uri>mailto:</uri>"));
100 content.replace(QRegularExpression(QStringLiteral("<cal-address>mailto:.*</cal-address>")), QStringLiteral("<cal-address>mailto:</cal-address>"));
101
102 content.replace(QRegularExpression(QStringLiteral("<uri>data:.*</uri>")), QStringLiteral("<uri>data:</uri>"));
103
104 content.replace(QRegularExpression(QStringLiteral("<last-modification-date>.*</last-modification-date>")),
105 QStringLiteral("<last-modification-date></last-modification-date>"));
106 // We no longer support pobox, so remove pobox lines
107 content.remove(QRegularExpression(QStringLiteral("<pobox>.*</pobox>")));
108
109 content.replace(QRegularExpression(QStringLiteral("<timestamp>.*</timestamp>")), QStringLiteral("<timestamp></timestamp>"));
110
111 // DotMatchesEverythingOption because <x-koblab-version> spans multiple lines
112 content.replace(QRegularExpression(QStringLiteral("<x-kolab-version>.*</x-kolab-version>"), QRegularExpression::DotMatchesEverythingOption),
113 QStringLiteral("<x-kolab-version></x-kolab-version>"));
114
115 content.replace(QRegularExpression(QStringLiteral("--nextPart\\S*")), QStringLiteral("--part"));
116 content.replace(QRegularExpression(QStringLiteral("\\bboundary=\"nextPart[^\\n]*")), QStringLiteral("boundary"));
117
118 content.replace(QRegularExpression(QStringLiteral("Date[^\\n]*")), QStringLiteral("Date"));
119 // The sort order of the attributes in kolabV2 is unpredictable
120 content.replace(QRegularExpression(QStringLiteral("<x-custom.*/>")), QStringLiteral("<x-custom/>"));
121 // quoted-printable encoding changes where the linebreaks are every now and then (an all are valid), so we remove the linebreaks
122 content.remove(QLatin1String("=\n"));
123 }
124
normalizeVCardMessage(QString content)125 QString normalizeVCardMessage(QString content)
126 {
127 // The encoding changes every now and then
128 content.replace(QRegularExpression(QStringLiteral("ENCODING=b;TYPE=png:.*")), QStringLiteral("ENCODING=b;TYPE=png:picturedata"));
129 return content;
130 }
131
132 // Normalize incidences for comparison
normalizeIncidence(KCalendarCore::Incidence::Ptr incidence)133 void normalizeIncidence(KCalendarCore::Incidence::Ptr incidence)
134 {
135 // The UID is not persistent (it's just the internal pointer), therefore we clear it
136 // TODO make sure that the UID does really not need to be persistent
137 auto attendees = incidence->attendees();
138 for (auto &attendee : attendees) {
139 attendee.setUid(attendee.email());
140 }
141 incidence->setAttendees(attendees);
142
143 // FIXME even if hasDueDate can differ, it shouldn't because it breaks equality. Check why they differ in the first place.
144 if (incidence->type() == KCalendarCore::IncidenceBase::TypeTodo) {
145 KCalendarCore::Todo::Ptr todo = incidence.dynamicCast<KCalendarCore::Todo>();
146 Q_ASSERT(todo.data());
147 if (!todo->hasDueDate() && !todo->hasStartDate()) {
148 todo->setAllDay(false); // all day has no meaning if there are no start and due dates but may differ nevertheless
149 }
150 }
151 }
152
LexicographicalCompare(const T & _x,const T & _y)153 template<template<typename> class Op, typename T> static bool LexicographicalCompare(const T &_x, const T &_y)
154 {
155 T x(_x);
156 x.setId(QString());
157 T y(_y);
158 y.setId(QString());
159 Op<QString> op;
160 return op(x.toString(), y.toString());
161 }
162
normalizePhoneNumbers(KContacts::Addressee & addressee,KContacts::Addressee & refAddressee)163 bool normalizePhoneNumbers(KContacts::Addressee &addressee, KContacts::Addressee &refAddressee)
164 {
165 KContacts::PhoneNumber::List phoneNumbers = addressee.phoneNumbers();
166 KContacts::PhoneNumber::List refPhoneNumbers = refAddressee.phoneNumbers();
167 if (phoneNumbers.size() != refPhoneNumbers.size()) {
168 return false;
169 }
170 std::sort(phoneNumbers.begin(), phoneNumbers.end(), LexicographicalCompare<std::less, KContacts::PhoneNumber>);
171 std::sort(refPhoneNumbers.begin(), refPhoneNumbers.end(), LexicographicalCompare<std::less, KContacts::PhoneNumber>);
172
173 for (int i = 0; i < phoneNumbers.size(); ++i) {
174 KContacts::PhoneNumber phoneNumber = phoneNumbers.at(i);
175 const KContacts::PhoneNumber refPhoneNumber = refPhoneNumbers.at(i);
176 KCOMPARE(LexicographicalCompare<std::equal_to>(phoneNumber, refPhoneNumber), true);
177 addressee.removePhoneNumber(phoneNumber);
178 phoneNumber.setId(refPhoneNumber.id());
179 addressee.insertPhoneNumber(phoneNumber);
180 // Make sure that both have the same sorted order
181 refAddressee.removePhoneNumber(refPhoneNumber);
182 refAddressee.insertPhoneNumber(refPhoneNumber);
183 }
184 // for ( int i = 0; i < phoneNumbers.size(); ++i ) {
185 // qDebug() << "--------------------------------------";
186 // qDebug() << addressee.phoneNumbers().at(i).toString();
187 // qDebug() << refAddressee.phoneNumbers().at(i).toString();
188 // }
189
190 return true;
191 }
192
normalizeAddresses(KContacts::Addressee & addressee,const KContacts::Addressee & refAddressee)193 bool normalizeAddresses(KContacts::Addressee &addressee, const KContacts::Addressee &refAddressee)
194 {
195 KContacts::Address::List addresses = addressee.addresses();
196 KContacts::Address::List refAddresses = refAddressee.addresses();
197 if (addresses.size() != refAddresses.size()) {
198 return false;
199 }
200 std::sort(addresses.begin(), addresses.end(), LexicographicalCompare<std::less, KContacts::Address>);
201 std::sort(refAddresses.begin(), refAddresses.end(), LexicographicalCompare<std::less, KContacts::Address>);
202
203 for (int i = 0; i < addresses.size(); ++i) {
204 KContacts::Address address = addresses.at(i);
205 const KContacts::Address refAddress = refAddresses.at(i);
206 KCOMPARE(LexicographicalCompare<std::equal_to>(address, refAddress), true);
207 addressee.removeAddress(address);
208 address.setId(refAddress.id());
209 addressee.insertAddress(address);
210 }
211
212 return true;
213 }
214
normalizeContact(KContacts::Addressee & addressee)215 void normalizeContact(KContacts::Addressee &addressee)
216 {
217 const KContacts::Address::List addresses = addressee.addresses();
218
219 for (KContacts::Address a : addresses) {
220 addressee.removeAddress(a);
221 a.setPostOfficeBox(QString()); // Not supported anymore
222 addressee.insertAddress(a);
223 }
224 addressee.setSound(KContacts::Sound()); // Sound is not supported
225
226 addressee.removeCustom(QStringLiteral("KOLAB"), QStringLiteral("CreationDate")); // The creation date is no longer existing
227
228 // Attachment names are no longer required because we identify the parts by cid and no longer by name
229 addressee.removeCustom(QStringLiteral("KOLAB"), QStringLiteral("LogoAttachmentName"));
230 addressee.removeCustom(QStringLiteral("KOLAB"), QStringLiteral("PictureAttachmentName"));
231 addressee.removeCustom(QStringLiteral("KOLAB"), QStringLiteral("SoundAttachmentName"));
232 }
233
createEvent(const Kolab::cDateTime & start,const Kolab::cDateTime & end)234 Kolab::Event createEvent(const Kolab::cDateTime &start, const Kolab::cDateTime &end)
235 {
236 Kolab::Event event;
237 event.setUid(QUuid::createUuid().toString().toStdString());
238 event.setStart(start);
239 event.setEnd(end);
240 return event;
241 }
242