File indexing completed on 2025-01-05 04:58:37
0001 /* 0002 * Copyright (C) 2016 Christian Mollekopf <chrigi_1@fastmail.fm> 0003 * 0004 * This program is free software; you can redistribute it and/or modify 0005 * it under the terms of the GNU General Public License as published by 0006 * the Free Software Foundation; either version 2 of the License, or 0007 * (at your option) any later version. 0008 * 0009 * This program is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 * GNU General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU General Public License 0015 * along with this program; if not, write to the 0016 * Free Software Foundation, Inc., 0017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 0018 */ 0019 #include <QTest> 0020 #include <QTcpSocket> 0021 0022 #include <tests/mailsynctest.h> 0023 #include "../imapresource.h" 0024 #include "../imapserverproxy.h" 0025 0026 #include "common/test.h" 0027 #include "common/domain/applicationdomaintype.h" 0028 #include "common/secretstore.h" 0029 #include "common/store.h" 0030 #include "common/resourcecontrol.h" 0031 #include "common/notifier.h" 0032 0033 using namespace Sink; 0034 using namespace Sink::ApplicationDomain; 0035 0036 /** 0037 * Test of complete system using the imap resource. 0038 * 0039 * This test requires the imap resource installed. 0040 */ 0041 class ImapMailSyncTest : public Sink::MailSyncTest 0042 { 0043 Q_OBJECT 0044 0045 protected: 0046 bool isBackendAvailable() Q_DECL_OVERRIDE 0047 { 0048 QTcpSocket socket; 0049 socket.connectToHost("localhost", 143); 0050 return socket.waitForConnected(200); 0051 } 0052 0053 void resetTestEnvironment() Q_DECL_OVERRIDE 0054 { 0055 system("resetmailbox.sh"); 0056 } 0057 0058 Sink::ApplicationDomain::SinkResource createResource() Q_DECL_OVERRIDE 0059 { 0060 auto resource = ApplicationDomain::ImapResource::create("account1"); 0061 resource.setProperty("server", "localhost"); 0062 resource.setProperty("port", 143); 0063 resource.setProperty("username", "doe"); 0064 resource.setProperty("daysToSync", 0); 0065 Sink::SecretStore::instance().insert(resource.identifier(), "doe"); 0066 return resource; 0067 } 0068 0069 Sink::ApplicationDomain::SinkResource createFaultyResource() Q_DECL_OVERRIDE 0070 { 0071 auto resource = ApplicationDomain::ImapResource::create("account1"); 0072 //We try to connect on localhost on port 0 because: 0073 //* Using a bogus ip instead of a bogus hostname avoids getting stuck in the hostname lookup. 0074 //* Using localhost avoids tcp trying to retransmit packets into nirvana 0075 //* Using port 0 fails immediately because it's not an existing port. 0076 //All we really want is something that immediately rejects our connection attempt, and this seems to work. 0077 resource.setProperty("server", "127.0.0.1"); 0078 resource.setProperty("port", 0); 0079 resource.setProperty("username", "doe"); 0080 Sink::SecretStore::instance().insert(resource.identifier(), "doe"); 0081 return resource; 0082 } 0083 0084 void removeResourceFromDisk(const QByteArray &identifier) Q_DECL_OVERRIDE 0085 { 0086 ::ImapResource::removeFromDisk(identifier); 0087 } 0088 0089 void createFolder(const QStringList &folderPath) Q_DECL_OVERRIDE 0090 { 0091 Imap::ImapServerProxy imap("localhost", 143, Imap::NoEncryption); 0092 VERIFYEXEC(imap.login("doe", "doe")); 0093 VERIFYEXEC(imap.create("INBOX." + folderPath.join('.'))); 0094 VERIFYEXEC(imap.subscribe("INBOX." + folderPath.join('.'))); 0095 } 0096 0097 void removeFolder(const QStringList &folderPath) Q_DECL_OVERRIDE 0098 { 0099 Imap::ImapServerProxy imap("localhost", 143, Imap::NoEncryption); 0100 VERIFYEXEC(imap.login("doe", "doe")); 0101 VERIFYEXEC(imap.remove("INBOX." + folderPath.join('.'))); 0102 } 0103 0104 QByteArray createMessage(const QStringList &folderPath, const QByteArray &message, const QDateTime &internalDate) 0105 { 0106 Imap::ImapServerProxy imap("localhost", 143, Imap::NoEncryption); 0107 VERIFYEXEC_RET(imap.login("doe", "doe"), {}); 0108 0109 auto appendJob = imap.append("INBOX." + folderPath.join('.'), message, {}, internalDate); 0110 auto future = appendJob.exec(); 0111 future.waitForFinished(); 0112 auto result = future.value(); 0113 return QByteArray::number(result); 0114 } 0115 0116 QByteArray createMessage(const QStringList &folderPath, const QByteArray &message) Q_DECL_OVERRIDE 0117 { 0118 return createMessage(folderPath, message, {}); 0119 } 0120 0121 void removeMessage(const QStringList &folderPath, const QByteArray &messages) Q_DECL_OVERRIDE 0122 { 0123 Imap::ImapServerProxy imap("localhost", 143, Imap::NoEncryption); 0124 VERIFYEXEC(imap.login("doe", "doe")); 0125 VERIFYEXEC(imap.remove("INBOX." + folderPath.join('.'), messages)); 0126 } 0127 0128 void markAsImportant(const QStringList &folderPath, const QByteArray &messageIdentifier) Q_DECL_OVERRIDE 0129 { 0130 Imap::ImapServerProxy imap("localhost", 143, Imap::NoEncryption); 0131 VERIFYEXEC(imap.login("doe", "doe")); 0132 VERIFYEXEC(imap.select("INBOX." + folderPath.join('.'))); 0133 VERIFYEXEC(imap.addFlags(KIMAP2::ImapSet::fromImapSequenceSet(messageIdentifier), QByteArrayList() << Imap::Flags::Flagged)); 0134 } 0135 0136 static QByteArray newMessage(const QString &subject, const QDateTime &dt = QDateTime::currentDateTimeUtc()) 0137 { 0138 auto msg = KMime::Message::Ptr::create(); 0139 msg->messageID(true)->generate("test.com"); 0140 msg->subject(true)->fromUnicodeString(subject, "utf8"); 0141 msg->date(true)->setDateTime(dt); 0142 msg->assemble(); 0143 return msg->encodedContent(true); 0144 } 0145 0146 private slots: 0147 void testNewMailNotification() 0148 { 0149 createFolder(QStringList() << "testNewMailNotification"); 0150 createMessage(QStringList() << "testNewMailNotification", newMessage("Foobar")); 0151 0152 const auto syncFolders = Sink::SyncScope{ApplicationDomain::getTypeName<Folder>()}.resourceFilter(mResourceInstanceIdentifier); 0153 //Fetch folders initially 0154 VERIFYEXEC(Store::synchronize(syncFolders)); 0155 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0156 0157 auto folder = Store::readOne<Folder>(Sink::Query{}.resourceFilter(mResourceInstanceIdentifier).filter<Folder::Name>("testNewMailNotification")); 0158 Q_ASSERT(!folder.identifier().isEmpty()); 0159 0160 const auto syncTestMails = Sink::SyncScope{ApplicationDomain::getTypeName<Mail>()}.resourceFilter(mResourceInstanceIdentifier).filter<Mail::Folder>(QVariant::fromValue(folder.identifier())); 0161 0162 bool notificationReceived = false; 0163 auto notifier = QSharedPointer<Sink::Notifier>::create(mResourceInstanceIdentifier); 0164 notifier->registerHandler([&](const Notification ¬ification) { 0165 if (notification.type == Sink::Notification::Info && notification.code == ApplicationDomain::NewContentAvailable && notification.entities.contains(folder.identifier())) { 0166 notificationReceived = true; 0167 } 0168 }); 0169 0170 //Should result in a change notification for test 0171 VERIFYEXEC(Store::synchronize(syncFolders)); 0172 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0173 0174 QTRY_VERIFY(notificationReceived); 0175 0176 notificationReceived = false; 0177 0178 //Fetch test mails to skip change notification 0179 VERIFYEXEC(Store::synchronize(syncTestMails)); 0180 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0181 0182 //Should no longer result in change notifications for test 0183 VERIFYEXEC(Store::synchronize(syncFolders)); 0184 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0185 0186 QVERIFY(!notificationReceived); 0187 0188 //Create message and retry 0189 createMessage(QStringList() << "testNewMailNotification", newMessage("This is a Subject.")); 0190 0191 //Should result in change notification 0192 VERIFYEXEC(Store::synchronize(syncFolders)); 0193 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0194 0195 QTRY_VERIFY(notificationReceived); 0196 } 0197 0198 void testSyncFolderBeforeFetchingNewMessages() 0199 { 0200 const auto syncScope = Sink::Query{}.resourceFilter(mResourceInstanceIdentifier); 0201 0202 createFolder(QStringList() << "test3"); 0203 0204 VERIFYEXEC(Store::synchronize(syncScope)); 0205 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0206 0207 createMessage(QStringList() << "test3", newMessage("Foobar")); 0208 0209 VERIFYEXEC(Store::synchronize(syncScope)); 0210 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0211 0212 auto mailQuery = Sink::Query{}.resourceFilter(mResourceInstanceIdentifier).request<Mail::Subject>().filter<Mail::Folder>(Sink::Query{}.filter<Folder::Name>("test3")); 0213 QCOMPARE(Store::read<Mail>(mailQuery).size(), 1); 0214 } 0215 0216 void testDateFilterSync() 0217 { 0218 auto dt = QDateTime{{2019, 04, 20}}; 0219 0220 //Create a folder 0221 createFolder({"datefilter"}); 0222 { 0223 Sink::Query query; 0224 query.setType<ApplicationDomain::Folder>(); 0225 VERIFYEXEC(Store::synchronize(query)); 0226 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0227 } 0228 0229 auto folder = Store::readOne<Folder>(Query{}.resourceFilter(mResourceInstanceIdentifier).filter<Folder::Name>("datefilter")); 0230 0231 //Create the two messsages with one matching the filter below the other not. 0232 createMessage({"datefilter"}, newMessage("1", dt.addDays(-4)), dt.addDays(-4)); 0233 createMessage({"datefilter"}, newMessage("2", dt.addDays(-2)), dt.addDays(-2)); 0234 0235 { 0236 Sink::Query query; 0237 query.setType<ApplicationDomain::Mail>(); 0238 query.resourceFilter(mResourceInstanceIdentifier); 0239 query.filter(ApplicationDomain::Mail::Date::name, QVariant::fromValue(dt.addDays(-3).date())); 0240 query.filter<Mail::Folder>(folder); 0241 0242 VERIFYEXEC(Store::synchronize(query)); 0243 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0244 } 0245 0246 //For the second message we should have the full payload, for the first only the headers 0247 { 0248 Sink::Query query; 0249 query.resourceFilter(mResourceInstanceIdentifier); 0250 query.filter<Mail::Folder>(folder); 0251 query.sort<ApplicationDomain::Mail::Date>(); 0252 auto mails = Store::read<Mail>(query); 0253 QCOMPARE(mails.size(), 2); 0254 QCOMPARE(mails.at(0).getFullPayloadAvailable(), true); 0255 QCOMPARE(mails.at(1).getFullPayloadAvailable(), false); 0256 } 0257 } 0258 0259 /* 0260 * Ensure that even though we have a date-filter we don't leave any gaps in the maillist. 0261 */ 0262 void testDateFilterForGaps() 0263 { 0264 auto dt = QDateTime{{2019, 04, 20}}; 0265 0266 auto foldername = "datefilter1"; 0267 createFolder({foldername}); 0268 createMessage({foldername}, newMessage("0", dt.addDays(-6)), dt.addDays(-6)); 0269 0270 VERIFYEXEC(Store::synchronize(Sink::SyncScope{})); 0271 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0272 0273 auto folder = Store::readOne<Folder>(Query{}.resourceFilter(mResourceInstanceIdentifier).filter<Folder::Name>(foldername)); 0274 0275 // We create two messages with one not matching the date filter below, and then ensure we get it nevertheless 0276 createMessage({foldername}, newMessage("1", dt.addDays(-4)), dt.addDays(-4)); 0277 createMessage({foldername}, newMessage("2", dt.addDays(-2)), dt.addDays(-2)); 0278 0279 { 0280 Sink::Query query; 0281 query.setType<ApplicationDomain::Mail>(); 0282 query.resourceFilter(mResourceInstanceIdentifier); 0283 query.filter(ApplicationDomain::Mail::Date::name, QVariant::fromValue(dt.addDays(-3).date())); 0284 query.filter<Mail::Folder>(folder); 0285 0286 VERIFYEXEC(Store::synchronize(query)); 0287 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0288 } 0289 0290 { 0291 Sink::Query query; 0292 query.resourceFilter(mResourceInstanceIdentifier); 0293 query.sort<ApplicationDomain::Mail::Date>(); 0294 query.filter<Mail::Folder>(folder); 0295 auto mails = Store::read<Mail>(query); 0296 QCOMPARE(mails.size(), 3); 0297 QCOMPARE(mails.at(0).getFullPayloadAvailable(), true); 0298 //We don't strictly have to pull the full payload for an item that is just fetched to ensure we have no missing mails, 0299 //but we currently do 0300 QCOMPARE(mails.at(1).getFullPayloadAvailable(), true); 0301 QCOMPARE(mails.at(2).getFullPayloadAvailable(), true); 0302 } 0303 } 0304 0305 /* 0306 * * First sync the folder 0307 * * Then create a message on the server 0308 * * Then attempt to sync it even though it doens't match the date filter. 0309 * We expect the message to be fetched with the payload even though it doesn't match the date-filter. 0310 */ 0311 void testDateFilterAfterInitialSync() 0312 { 0313 auto dt = QDateTime{{2019, 04, 20}}; 0314 0315 auto foldername = "datefilter2"; 0316 createFolder({foldername}); 0317 0318 Sink::SyncScope query; 0319 query.setType<ApplicationDomain::Folder>(); 0320 VERIFYEXEC(Store::synchronize(query)); 0321 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0322 auto folder = Store::readOne<Folder>(Query{}.resourceFilter(mResourceInstanceIdentifier).filter<Folder::Name>(foldername)); 0323 0324 { 0325 Sink::Query query; 0326 query.setType<ApplicationDomain::Mail>(); 0327 query.resourceFilter(mResourceInstanceIdentifier); 0328 query.filter<Mail::Folder>(folder); 0329 0330 VERIFYEXEC(Store::synchronize(query)); 0331 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0332 } 0333 0334 createMessage({foldername}, newMessage("0", dt.addDays(-6)), dt.addDays(-6)); 0335 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0336 0337 { 0338 Sink::Query query; 0339 query.setType<ApplicationDomain::Mail>(); 0340 query.resourceFilter(mResourceInstanceIdentifier); 0341 query.filter(ApplicationDomain::Mail::Date::name, QVariant::fromValue(dt.addDays(-3).date())); 0342 query.filter<Mail::Folder>(folder); 0343 0344 VERIFYEXEC(Store::synchronize(query)); 0345 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0346 } 0347 0348 { 0349 Sink::Query query; 0350 query.resourceFilter(mResourceInstanceIdentifier); 0351 query.sort<ApplicationDomain::Mail::Date>(); 0352 query.filter<Mail::Folder>(folder); 0353 auto mails = Store::read<Mail>(query); 0354 QCOMPARE(mails.size(), 1); 0355 QCOMPARE(mails.at(0).getFullPayloadAvailable(), true); 0356 } 0357 } 0358 0359 /** 0360 * The mails are too old so we only fetch headers 0361 */ 0362 void testDateFilterFetchHeadersOnly() 0363 { 0364 auto dt = QDateTime{{2019, 04, 20}}; 0365 0366 auto foldername = "datefilter3"; 0367 createFolder({foldername}); 0368 createMessage({foldername}, newMessage("0", dt.addDays(-30)), dt.addDays(-30)); 0369 0370 Sink::SyncScope query; 0371 query.setType<ApplicationDomain::Folder>(); 0372 VERIFYEXEC(Store::synchronize(query)); 0373 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0374 auto folder = Store::readOne<Folder>(Query{}.resourceFilter(mResourceInstanceIdentifier).filter<Folder::Name>(foldername)); 0375 0376 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0377 0378 { 0379 Sink::Query query; 0380 query.setType<ApplicationDomain::Mail>(); 0381 query.resourceFilter(mResourceInstanceIdentifier); 0382 query.filter(ApplicationDomain::Mail::Date::name, QVariant::fromValue(dt.addDays(-3).date())); 0383 query.filter<Mail::Folder>(folder); 0384 0385 VERIFYEXEC(Store::synchronize(query)); 0386 VERIFYEXEC(ResourceControl::flushMessageQueue(mResourceInstanceIdentifier)); 0387 } 0388 0389 { 0390 Sink::Query query; 0391 query.resourceFilter(mResourceInstanceIdentifier); 0392 query.sort<ApplicationDomain::Mail::Date>(); 0393 query.filter<Mail::Folder>(folder); 0394 auto mails = Store::read<Mail>(query); 0395 QCOMPARE(mails.size(), 1); 0396 QCOMPARE(mails.at(0).getFullPayloadAvailable(), false); 0397 } 0398 } 0399 }; 0400 0401 QTEST_MAIN(ImapMailSyncTest) 0402 0403 #include "imapmailsynctest.moc"