File indexing completed on 2024-05-19 05:22:17
0001 /* 0002 Copyright (c) 2018 Christian Mollekopf <mollekopf@kolabsys.com> 0003 0004 This library is free software; you can redistribute it and/or modify it 0005 under the terms of the GNU Library General Public License as published by 0006 the Free Software Foundation; either version 2 of the License, or (at your 0007 option) any later version. 0008 0009 This library is distributed in the hope that it will be useful, but WITHOUT 0010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 0011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 0012 License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this library; see the file COPYING.LIB. If not, write to the 0016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 0017 02110-1301, USA. 0018 */ 0019 #include "teststore.h" 0020 0021 #include <sink/store.h> 0022 #include <sink/resourcecontrol.h> 0023 #include <sink/secretstore.h> 0024 0025 #include <kmime/kmime_message.h> 0026 0027 #include <KCalendarCore/Event> 0028 #include <KCalendarCore/Todo> 0029 #include <KCalendarCore/ICalFormat> 0030 #include <KContacts/VCardConverter> 0031 0032 #include <QDebug> 0033 #include <QUuid> 0034 #include <QVariant> 0035 0036 #include "framework/src/domain/mime/mailtemplates.h" 0037 #include "framework/src/keyring.h" 0038 0039 using namespace Kube; 0040 0041 static void iterateOverObjects(const QVariantList &list, std::function<void(const QVariantMap &)> callback) 0042 { 0043 for (const auto &entry : list) { 0044 auto object = entry.toMap(); 0045 callback(object); 0046 } 0047 } 0048 0049 static QStringList toStringList(const QVariantList &list) 0050 { 0051 QStringList s; 0052 for (const auto &e : list) { 0053 s << e.toString(); 0054 } 0055 return s; 0056 } 0057 0058 static QByteArrayList toByteArrayList(const QVariantList &list) 0059 { 0060 QByteArrayList s; 0061 for (const auto &e : list) { 0062 s << e.toByteArray(); 0063 } 0064 return s; 0065 } 0066 0067 static void createMail(const QVariantMap &object, const QByteArray &folder = {}, const QByteArray &resourceId = {}) 0068 { 0069 using namespace Sink::ApplicationDomain; 0070 0071 auto toAddresses = toStringList(object["to"].toList()); 0072 auto ccAddresses = toStringList(object["cc"].toList()); 0073 auto bccAddresses = toStringList(object["bcc"].toList()); 0074 0075 QList<Attachment> attachments = {}; 0076 if (object.contains("attachments")) { 0077 auto attachmentSpecs = object["attachments"].toList(); 0078 for (int i = 0; i < attachmentSpecs.size(); ++i) { 0079 auto const &spec = attachmentSpecs.at(i).toMap(); 0080 attachments << Attachment{spec["name"].toString(), 0081 spec["name"].toString(), 0082 spec["mimeType"].toByteArray(), 0083 false, 0084 spec["data"].toByteArray()}; 0085 } 0086 } 0087 0088 KMime::Types::Mailbox from; 0089 if (object.contains("from")) { 0090 from.fromUnicodeString(object["from"].toString()); 0091 } else { 0092 from.fromUnicodeString("identity@example.org"); 0093 } 0094 auto msg = MailTemplates::createMessage({}, 0095 toAddresses, 0096 ccAddresses, 0097 bccAddresses, 0098 from, 0099 object["subject"].toString(), 0100 object["body"].toString(), 0101 object["bodyIsHtml"].toBool(), 0102 attachments, 0103 {}, 0104 {}); 0105 Q_ASSERT(msg); 0106 if (object.contains("messageId")) { 0107 msg->messageID(true)->from7BitString(object["messageId"].toByteArray()); 0108 } 0109 if (object.contains("inReplyTo")) { 0110 msg->inReplyTo(true)->from7BitString(object["inReplyTo"].toByteArray()); 0111 } 0112 if (object.contains("date")) { 0113 msg->date(true)->setDateTime(QDateTime::fromString(object["date"].toString(), Qt::ISODate)); 0114 } 0115 0116 msg->assemble(); 0117 0118 auto res = resourceId; 0119 if (res.isEmpty()) { 0120 res = object["resource"].toByteArray(); 0121 } 0122 auto mail = ApplicationDomainType::createEntity<Mail>(res); 0123 mail.setMimeMessage(msg->encodedContent(true)); 0124 mail.setUnread(object["unread"].toBool()); 0125 mail.setDraft(object["draft"].toBool()); 0126 mail.setImportant(object["important"].toBool()); 0127 if (!folder.isEmpty()) { 0128 mail.setFolder(folder); 0129 } 0130 Sink::Store::create(mail).exec().waitForFinished(); 0131 } 0132 0133 static void createFolder(const QVariantMap &object, const QByteArray &folderId = {}) 0134 { 0135 using namespace Sink::ApplicationDomain; 0136 auto resourceId = object["resource"].toByteArray(); 0137 auto folder = ApplicationDomainType::createEntity<Folder>(resourceId); 0138 folder.setName(object["name"].toString()); 0139 folder.setSpecialPurpose(toByteArrayList(object["specialpurpose"].toList())); 0140 if (!folderId.isEmpty()) { 0141 folder.setParent(folderId); 0142 } 0143 Sink::Store::create(folder).exec().waitForFinished(); 0144 0145 iterateOverObjects(object.value("mails").toList(), [=](const QVariantMap &object) { 0146 createMail(object, folder.identifier(), resourceId); 0147 }); 0148 iterateOverObjects(object.value("folders").toList(), [=](const QVariantMap &object) { 0149 createFolder(object, folder.identifier()); 0150 }); 0151 } 0152 0153 static void createEvent(const QVariantMap &object, const QByteArray &calendarId, const QByteArray &resourceId) 0154 { 0155 using Sink::ApplicationDomain::ApplicationDomainType; 0156 using Sink::ApplicationDomain::Event; 0157 0158 auto sinkEvent = ApplicationDomainType::createEntity<Event>(resourceId); 0159 0160 auto calcoreEvent = QSharedPointer<KCalendarCore::Event>::create(); 0161 0162 QString uid; 0163 if (object.contains("uid")) { 0164 uid = object["uid"].toString(); 0165 } else { 0166 uid = QUuid::createUuid().toString(); 0167 } 0168 calcoreEvent->setUid(uid); 0169 0170 auto summary = object["summary"].toString(); 0171 calcoreEvent->setSummary(summary); 0172 0173 if (object.contains("description")) { 0174 calcoreEvent->setDescription(object["description"].toString()); 0175 } 0176 0177 if (object.contains("location")) { 0178 calcoreEvent->setLocation(object["location"].toString()); 0179 } 0180 0181 auto startTime = object["starts"].toDateTime(); 0182 auto endTime = object["ends"].toDateTime(); 0183 0184 calcoreEvent->setDtStart(startTime); 0185 calcoreEvent->setDtEnd(endTime); 0186 0187 if (object.contains("allDay")) { 0188 calcoreEvent->setAllDay(object["allDay"].toBool()); 0189 } 0190 0191 if (object.contains("organizer")) { 0192 calcoreEvent->setOrganizer(object["organizer"].toString()); 0193 } 0194 if (object.contains("attendees")) { 0195 for (const auto &attendee : object["attendees"].toList()) { 0196 auto map = attendee.toMap(); 0197 calcoreEvent->addAttendee(KCalendarCore::Attendee(map["name"].toString(), map["email"].toString(), true, KCalendarCore::Attendee::NeedsAction, KCalendarCore::Attendee::ReqParticipant, QString{})); 0198 } 0199 } 0200 0201 sinkEvent.setIcal(KCalendarCore::ICalFormat().toICalString(calcoreEvent).toUtf8()); 0202 0203 sinkEvent.setCalendar(calendarId); 0204 0205 Sink::Store::create(sinkEvent).exec().waitForFinished(); 0206 } 0207 0208 static void createTodo(const QVariantMap &object, const QByteArray &calendarId, const QByteArray &resourceId, const QByteArray &parentUid = {}) 0209 { 0210 using Sink::ApplicationDomain::ApplicationDomainType; 0211 using Sink::ApplicationDomain::Todo; 0212 0213 auto sinkEvent = ApplicationDomainType::createEntity<Todo>(resourceId); 0214 0215 auto calcoreEvent = QSharedPointer<KCalendarCore::Todo>::create(); 0216 0217 QString uid; 0218 if (object.contains("uid")) { 0219 uid = object["uid"].toString(); 0220 } else { 0221 uid = QUuid::createUuid().toString(); 0222 } 0223 calcoreEvent->setUid(uid); 0224 0225 if (!parentUid.isEmpty()) { 0226 calcoreEvent->setRelatedTo(parentUid); 0227 } 0228 0229 auto summary = object["summary"].toString(); 0230 calcoreEvent->setSummary(summary); 0231 0232 if (object.contains("description")) { 0233 calcoreEvent->setDescription(object["description"].toString()); 0234 } 0235 0236 calcoreEvent->setDtStart(object["starts"].toDateTime()); 0237 calcoreEvent->setDtDue(object["due"].toDateTime()); 0238 0239 if (object["doing"].toBool()) { 0240 calcoreEvent->setCompleted(false); 0241 calcoreEvent->setStatus(KCalendarCore::Incidence::StatusInProcess); 0242 } 0243 if (object["done"].toBool()) { 0244 calcoreEvent->setCompleted(true); 0245 calcoreEvent->setStatus(KCalendarCore::Incidence::StatusCompleted); 0246 } 0247 if (object["needsAction"].toBool()) { 0248 calcoreEvent->setStatus(KCalendarCore::Incidence::StatusNeedsAction); 0249 } 0250 0251 sinkEvent.setIcal(KCalendarCore::ICalFormat().toICalString(calcoreEvent).toUtf8()); 0252 0253 sinkEvent.setCalendar(calendarId); 0254 0255 Sink::Store::create(sinkEvent).exec().waitForFinished(); 0256 0257 iterateOverObjects(object.value("subtodos").toList(), 0258 [calendarId, resourceId, uid](const QVariantMap &object) { createTodo(object, calendarId, resourceId, uid.toUtf8()); }); 0259 0260 } 0261 0262 static void createCalendar(const QVariantMap &object) 0263 { 0264 using Sink::ApplicationDomain::Calendar; 0265 using Sink::ApplicationDomain::ApplicationDomainType; 0266 0267 auto resourceId = object["resource"].toByteArray(); 0268 auto calendar = ApplicationDomainType::createEntity<Calendar>(resourceId); 0269 calendar.setName(object["name"].toString()); 0270 calendar.setColor(object["color"].toByteArray()); 0271 calendar.setEnabled(object["enabled"].toBool()); 0272 if (object.contains("contentTypes")) { 0273 calendar.setContentTypes(toByteArrayList(object["contentTypes"].toList())); 0274 } else { 0275 calendar.setContentTypes({"event", "todo"}); 0276 } 0277 Sink::Store::create(calendar).exec().waitForFinished(); 0278 0279 auto calendarId = calendar.identifier(); 0280 iterateOverObjects(object.value("events").toList(), 0281 [calendarId, resourceId](const QVariantMap &object) { createEvent(object, calendarId, resourceId); }); 0282 iterateOverObjects(object.value("todos").toList(), 0283 [calendarId, resourceId](const QVariantMap &object) { createTodo(object, calendarId, resourceId); }); 0284 } 0285 0286 static void createContact(const QVariantMap &object, const QByteArray &addressbookId = {}, const QByteArray &resourceId = {}) 0287 { 0288 using Sink::ApplicationDomain::ApplicationDomainType; 0289 using Sink::ApplicationDomain::Contact; 0290 0291 QString uid; 0292 if (object.contains("uid")) { 0293 uid = object["uid"].toString(); 0294 } else { 0295 uid = QUuid::createUuid().toString(); 0296 } 0297 0298 KContacts::Addressee addressee; 0299 addressee.setUid(uid); 0300 addressee.setGivenName(object["givenname"].toString()); 0301 addressee.setFamilyName(object["familyname"].toString()); 0302 addressee.setFormattedName(object["givenname"].toString() + " " + object["familyname"].toString()); 0303 addressee.setEmails(object["email"].toStringList()); 0304 0305 KContacts::VCardConverter converter; 0306 0307 auto res = resourceId; 0308 if (res.isEmpty()) { 0309 res = object["resource"].toByteArray(); 0310 } 0311 auto contact = ApplicationDomainType::createEntity<Contact>(res); 0312 contact.setVcard(converter.createVCard(addressee, KContacts::VCardConverter::v3_0)); 0313 contact.setAddressbook(addressbookId); 0314 0315 Sink::Store::create(contact).exec().waitForFinished(); 0316 } 0317 0318 static void createAddressbook(const QVariantMap &object) 0319 { 0320 using namespace Sink::ApplicationDomain; 0321 auto resourceId = object["resource"].toByteArray(); 0322 auto addressbook = ApplicationDomainType::createEntity<Addressbook>(resourceId); 0323 addressbook.setName(object["name"].toString()); 0324 Sink::Store::create(addressbook).exec().waitForFinished(); 0325 0326 if (object.contains("generator")) { 0327 const auto generator = object.value("generator").toMap(); 0328 const auto _template = generator["template"].toMap(); 0329 const auto count = generator["count"].toInt(); 0330 const auto key = generator["key"].toString(); 0331 0332 for (int i = 0; i < count; i++) { 0333 auto _object = _template; 0334 const auto replacement = key + QString::number(i); 0335 _object.insert("givenname", _object["givenname"].toString().replace(key, replacement)); 0336 _object.insert("email", _object["email"].toStringList().replaceInStrings(key, replacement)); 0337 0338 createContact(_object, addressbook.identifier(), resourceId); 0339 } 0340 } 0341 0342 iterateOverObjects(object.value("contacts").toList(), [=](const QVariantMap &object) { 0343 createContact(object, addressbook.identifier(), resourceId); 0344 }); 0345 } 0346 0347 void TestStore::setup(const QVariantMap &map) 0348 { 0349 using namespace Sink::ApplicationDomain; 0350 0351 //Cleanup any old data 0352 const auto accounts = Sink::Store::read<SinkAccount>({}); 0353 for (const auto &account : accounts) { 0354 Sink::Store::remove(account).exec().waitForFinished(); 0355 } 0356 0357 iterateOverObjects(map.value("accounts").toList(), [&] (const QVariantMap &object) { 0358 auto account = ApplicationDomainType::createEntity<SinkAccount>("", object["id"].toByteArray()); 0359 account.setName(object["name"].toString()); 0360 Kube::Keyring::instance()->unlock(account.identifier()); 0361 Sink::Store::create(account).exec().waitForFinished(); 0362 }); 0363 QByteArrayList resources; 0364 iterateOverObjects(map.value("resources").toList(), [&] (const QVariantMap &object) { 0365 resources << object["id"].toByteArray(); 0366 auto resource = [&] { 0367 using namespace Sink::ApplicationDomain; 0368 auto resource = ApplicationDomainType::createEntity<SinkResource>("", object["id"].toByteArray()); 0369 if (object["type"] == "dummy") { 0370 resource.setResourceType("sink.dummy"); 0371 } else if (object["type"] == "mailtransport") { 0372 resource.setResourceType("sink.mailtransport"); 0373 resource.setProperty("testmode", true); 0374 } else if (object["type"] == "caldav") { 0375 resource.setResourceType("sink.caldav"); 0376 resource.setProperty("testmode", true); 0377 } else if (object["type"] == "carddav") { 0378 resource.setResourceType("sink.carddav"); 0379 resource.setProperty("testmode", true); 0380 } else { 0381 Q_ASSERT(false); 0382 } 0383 return resource; 0384 }(); 0385 resource.setAccount(object["account"].toByteArray()); 0386 Sink::Store::create(resource).exec().waitForFinished(); 0387 Sink::SecretStore::instance().insert(resource.identifier(), "secret"); 0388 }); 0389 0390 iterateOverObjects(map.value("identities").toList(), [] (const QVariantMap &object) { 0391 auto identity = Sink::ApplicationDomain::Identity{}; 0392 identity.setAccount(object["account"].toByteArray()); 0393 identity.setAddress(object["address"].toString()); 0394 identity.setName(object["name"].toString()); 0395 Sink::Store::create(identity).exec().waitForFinished(); 0396 }); 0397 0398 iterateOverObjects(map.value("folders").toList(), [] (const QVariantMap &map) { 0399 createFolder(map); 0400 }); 0401 iterateOverObjects(map.value("mails").toList(), [] (const QVariantMap &map) { 0402 createMail(map); 0403 }); 0404 0405 iterateOverObjects(map.value("calendars").toList(), createCalendar); 0406 iterateOverObjects(map.value("addressbooks").toList(), createAddressbook); 0407 0408 Sink::ResourceControl::flushMessageQueue(resources).exec().waitForFinished(); 0409 } 0410 0411 void TestStore::shutdownResources() 0412 { 0413 const auto resources = Sink::Store::read<Sink::ApplicationDomain::SinkResource>({}); 0414 for (const auto &resource : resources) { 0415 Sink::ResourceControl::shutdown(resource.identifier()).exec().waitForFinished(); 0416 } 0417 } 0418 0419 QVariant TestStore::load(const QByteArray &type, const QVariantMap &filter) 0420 { 0421 using namespace Sink::ApplicationDomain; 0422 const auto list = loadList(type, filter); 0423 if (!list.isEmpty()) { 0424 if (list.size() > 1) { 0425 qWarning() << "While loading" << type << "with filter" << filter 0426 << "; got multiple elements, but returning the first one."; 0427 } 0428 return list.first(); 0429 } 0430 return {}; 0431 } 0432 template <typename T> 0433 QVariantList toVariantList(const QList<T> &list) 0434 { 0435 QVariantList result; 0436 std::transform(list.constBegin(), list.constEnd(), std::back_inserter(result), [] (const T &m) { 0437 return QVariant::fromValue(T::Ptr::create(m)); 0438 }); 0439 Q_ASSERT(list.size() == result.size()); 0440 return result; 0441 } 0442 0443 QVariantList TestStore::loadList(const QByteArray &type, const QVariantMap &_filter) 0444 { 0445 auto filter = _filter; 0446 using namespace Sink::ApplicationDomain; 0447 Sink::Query query; 0448 if (filter.contains("resource")) { 0449 query.resourceFilter(filter.take("resource").toByteArray()); 0450 } 0451 0452 for (auto it = filter.begin(); it != filter.end(); ++it) { 0453 query.filter(it.key().toUtf8(), {it.value()}); 0454 } 0455 0456 if (type == "mail") { 0457 return toVariantList(Sink::Store::read<Mail>(query)); 0458 } 0459 if (type == "folder") { 0460 return toVariantList(Sink::Store::read<Folder>(query)); 0461 } 0462 if (type == "resource") { 0463 return toVariantList(Sink::Store::read<SinkResource>(query)); 0464 } 0465 if (type == "account") { 0466 return toVariantList(Sink::Store::read<SinkAccount>(query)); 0467 } 0468 0469 Q_ASSERT(false); 0470 return {}; 0471 } 0472 0473 QVariantMap TestStore::read(const QVariant &object) 0474 { 0475 using namespace Sink::ApplicationDomain; 0476 QVariantMap map; 0477 if (auto mail = object.value<Mail::Ptr>()) { 0478 map.insert("uid", mail->identifier()); 0479 map.insert("subject", mail->getSubject()); 0480 map.insert("draft", mail->getDraft()); 0481 return map; 0482 } 0483 Q_ASSERT(false); 0484 return {}; 0485 }