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