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 }