File indexing completed on 2024-11-17 04:45:04
0001 /* 0002 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 0003 SPDX-FileContributor: Kevin Ottens <kevin@kdab.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "imaptestbase.h" 0009 0010 #include "retrievecollectionmetadatatask.h" 0011 0012 #include "imapaclattribute.h" 0013 #include "imapquotaattribute.h" 0014 #include "noinferiorsattribute.h" 0015 #include "noselectattribute.h" 0016 #include <Akonadi/AttributeFactory> 0017 #include <Akonadi/CollectionAnnotationsAttribute> 0018 #include <Akonadi/CollectionQuotaAttribute> 0019 #include <QTest> 0020 using QBYTEARRAYMAP = QMap<QByteArray, QByteArray>; 0021 using QBYTEARRAYINT64MAP = QMap<QByteArray, qint64>; 0022 0023 Q_DECLARE_METATYPE(Akonadi::Collection::Rights) 0024 Q_DECLARE_METATYPE(QBYTEARRAYMAP) 0025 0026 class TestRetrieveCollectionMetadataTask : public ImapTestBase 0027 { 0028 Q_OBJECT 0029 0030 private Q_SLOTS: 0031 0032 void initTestCase() 0033 { 0034 Akonadi::AttributeFactory::registerAttribute<Akonadi::ImapAclAttribute>(); 0035 Akonadi::AttributeFactory::registerAttribute<NoSelectAttribute>(); 0036 } 0037 0038 void shouldCollectionRetrieveMetadata_data() 0039 { 0040 QTest::addColumn<Akonadi::Collection>("collection"); 0041 QTest::addColumn<QStringList>("capabilities"); 0042 QTest::addColumn<QList<QByteArray>>("scenario"); 0043 QTest::addColumn<QStringList>("callNames"); 0044 QTest::addColumn<Akonadi::Collection::Rights>("expectedRights"); 0045 QTest::addColumn<QBYTEARRAYMAP>("expectedAnnotations"); 0046 QTest::addColumn<QList<QByteArray>>("expectedRoots"); 0047 QTest::addColumn<QList<QBYTEARRAYINT64MAP>>("expectedLimits"); 0048 QTest::addColumn<QList<QBYTEARRAYINT64MAP>>("expectedUsages"); 0049 0050 Akonadi::Collection collection; 0051 QStringList capabilities; 0052 QList<QByteArray> scenario; 0053 QStringList callNames; 0054 QMap<QByteArray, QByteArray> expectedAnnotations; 0055 QList<QByteArray> expectedRoots; 0056 QList<QMap<QByteArray, qint64>> expectedLimits; 0057 QList<QMap<QByteArray, qint64>> expectedUsages; 0058 0059 expectedRoots.clear(); 0060 expectedLimits.clear(); 0061 expectedUsages.clear(); 0062 0063 collection = createCollectionChain(QStringLiteral("/INBOX/Foo")); 0064 collection.setRights(Akonadi::Collection::ReadOnly); 0065 0066 capabilities.clear(); 0067 capabilities << QStringLiteral("ANNOTATEMORE") << QStringLiteral("ACL") << QStringLiteral("QUOTA"); 0068 0069 scenario.clear(); 0070 scenario << defaultPoolConnectionScenario() << R"(C: A000003 GETANNOTATION "INBOX/Foo" "*" "value.shared")" 0071 << "S: * ANNOTATION INBOX/Foo /vendor/kolab/folder-test ( value.shared true )" 0072 << "S: A000003 OK annotations retrieved" 0073 << "C: A000004 MYRIGHTS \"INBOX/Foo\"" 0074 << "S: * MYRIGHTS \"INBOX/Foo\" lrswipkxtecda" 0075 << "S: A000004 OK rights retrieved" 0076 << "C: A000005 GETQUOTAROOT \"INBOX/Foo\"" 0077 << "S: * QUOTAROOT INBOX/Foo user/foo" 0078 << "S: * QUOTA user/foo ( )" 0079 << "S: A000005 OK quota retrieved" 0080 << "C: A000006 GETACL \"INBOX/Foo\"" 0081 << "S: * ACL INBOX/Foo foo@kde.org lrswipcda" 0082 << "S: A000006 OK acl retrieved"; 0083 0084 callNames.clear(); 0085 callNames << QStringLiteral("collectionAttributesRetrieved"); 0086 0087 expectedAnnotations.clear(); 0088 expectedAnnotations.insert("/shared/vendor/kolab/folder-test", "true"); 0089 0090 Akonadi::Collection::Rights rights = Akonadi::Collection::AllRights; 0091 QTest::newRow("first listing, connected IMAP") 0092 << collection << capabilities << scenario << callNames << rights << expectedAnnotations << expectedRoots << expectedLimits << expectedUsages; 0093 0094 // 0095 // Test that if the parent collection doesn't allow renaming in its ACL, the child mailbox 0096 // can't be renamed, i.e. doesn't have the CanChangeCollection flag. 0097 // 0098 Akonadi::Collection parentCollection = createCollectionChain(QStringLiteral("/INBOX")); 0099 QMap<QByteArray, KIMAP::Acl::Rights> rightsMap; 0100 rightsMap.insert("Hans", 0101 KIMAP::Acl::Lookup | KIMAP::Acl::Read | KIMAP::Acl::KeepSeen | KIMAP::Acl::Write | KIMAP::Acl::Insert | KIMAP::Acl::Post 0102 | KIMAP::Acl::Delete); 0103 auto aclAttribute = new Akonadi::ImapAclAttribute(); 0104 aclAttribute->setRights(rightsMap); 0105 parentCollection.addAttribute(aclAttribute); 0106 collection.setParentCollection(parentCollection); 0107 rights = Akonadi::Collection::AllRights; 0108 rights &= ~Akonadi::Collection::CanChangeCollection; 0109 QTest::newRow("parent without create rights") << collection << capabilities << scenario << callNames << rights << expectedAnnotations << expectedRoots 0110 << expectedLimits << expectedUsages; 0111 0112 // 0113 // Test that if the parent collection is a noselect folder, the child mailbox will not have 0114 // rename (CanChangeCollection) permission. 0115 // 0116 parentCollection = createCollectionChain(QStringLiteral("/INBOX")); 0117 auto noSelectAttribute = new NoSelectAttribute(); 0118 parentCollection.addAttribute(noSelectAttribute); 0119 collection.setParentCollection(parentCollection); 0120 QTest::newRow("parent with noselect") << collection << capabilities << scenario << callNames << rights << expectedAnnotations << expectedRoots 0121 << expectedLimits << expectedUsages; 0122 parentCollection.removeAttribute<NoSelectAttribute>(); 0123 0124 // 0125 // Test that the rights are properly set on the resulting collection if the mailbox doesn't 0126 // have full rights. 0127 // 0128 collection.setParentCollection(createCollectionChain(QStringLiteral("/INBOX"))); 0129 scenario.clear(); 0130 scenario << defaultPoolConnectionScenario() << R"(C: A000003 GETANNOTATION "INBOX/Foo" "*" "value.shared")" 0131 << "S: * ANNOTATION INBOX/Foo /vendor/kolab/folder-test ( value.shared true )" 0132 << "S: A000003 OK annotations retrieved" 0133 << "C: A000004 MYRIGHTS \"INBOX/Foo\"" 0134 << "S: * MYRIGHTS \"INBOX/Foo\" wi" 0135 << "S: A000004 OK rights retrieved" 0136 << "C: A000005 GETQUOTAROOT \"INBOX/Foo\"" 0137 << "S: * QUOTAROOT INBOX/Foo user/foo" 0138 << "S: * QUOTA user/foo ( )" 0139 << "S: A000005 OK quota retrieved"; 0140 rights = Akonadi::Collection::CanCreateItem | Akonadi::Collection::CanChangeItem | Akonadi::Collection::CanChangeCollection; 0141 QTest::newRow("only some rights") << collection << capabilities << scenario << callNames << rights << expectedAnnotations << expectedRoots 0142 << expectedLimits << expectedUsages; 0143 0144 // 0145 // Test that a warning is issued if the insert rights of a folder have been revoked on the server. 0146 // 0147 collection = createCollectionChain(QStringLiteral("/INBOX/Foo")); 0148 collection.setParentCollection(createCollectionChain(QStringLiteral("/INBOX"))); 0149 // We use the aclattribute to determine if a collection already has acl's or not 0150 collection.addAttribute(new Akonadi::ImapAclAttribute()); 0151 collection.setRights(Akonadi::Collection::CanCreateItem); 0152 0153 capabilities.clear(); 0154 capabilities << QStringLiteral("ANNOTATEMORE") << QStringLiteral("ACL") << QStringLiteral("QUOTA"); 0155 0156 scenario.clear(); 0157 scenario << defaultPoolConnectionScenario() << R"(C: A000003 GETANNOTATION "INBOX/Foo" "*" "value.shared")" 0158 << "S: * ANNOTATION INBOX/Foo /vendor/kolab/folder-test ( value.shared true )" 0159 << "S: A000003 OK annotations retrieved" 0160 << "C: A000004 MYRIGHTS \"INBOX/Foo\"" 0161 << "S: * MYRIGHTS \"INBOX/Foo\" w" 0162 << "S: A000004 OK rights retrieved" 0163 << "C: A000005 GETQUOTAROOT \"INBOX/Foo\"" 0164 << "S: * QUOTAROOT INBOX/Foo user/foo" 0165 << "S: * QUOTA user/foo ( )" 0166 << "S: A000005 OK quota retrieved"; 0167 0168 callNames.clear(); 0169 callNames << QStringLiteral("showInformationDialog"); 0170 callNames << QStringLiteral("collectionAttributesRetrieved"); 0171 0172 rights = Akonadi::Collection::CanChangeItem | Akonadi::Collection::CanChangeCollection; 0173 QTest::newRow("revoked rights") << collection << capabilities << scenario << callNames << rights << expectedAnnotations << expectedRoots 0174 << expectedLimits << expectedUsages; 0175 0176 // 0177 // Test that NoInferiors overrides acl rights and disallows creating new mailboxes 0178 // 0179 collection = createCollectionChain(QStringLiteral("/INBOX/Foo")); 0180 collection.setParentCollection(createCollectionChain(QString())); 0181 collection.setRemoteId(QStringLiteral("/INBOX")); 0182 collection.setRights(Akonadi::Collection::AllRights); 0183 collection.addAttribute(new NoInferiorsAttribute(true)); 0184 scenario.clear(); 0185 scenario << defaultPoolConnectionScenario() << R"(C: A000003 GETANNOTATION "INBOX" "*" "value.shared")" 0186 << "S: * ANNOTATION INBOX /vendor/kolab/folder-test ( value.shared true )" 0187 << "S: A000003 OK annotations retrieved" 0188 << "C: A000004 MYRIGHTS \"INBOX\"" 0189 << "S: * MYRIGHTS \"INBOX\" wk" 0190 << "S: A000004 OK rights retrieved" 0191 << "C: A000005 GETQUOTAROOT \"INBOX\"" 0192 << "S: * QUOTAROOT INBOX user" 0193 << "S: * QUOTA user ( )" 0194 << "S: A000005 OK quota retrieved"; 0195 0196 callNames.clear(); 0197 callNames << QStringLiteral("collectionAttributesRetrieved"); 0198 0199 rights = Akonadi::Collection::CanChangeItem | Akonadi::Collection::CanChangeCollection; 0200 0201 QTest::newRow("noinferiors") << collection << capabilities << scenario << callNames << rights << expectedAnnotations << expectedRoots << expectedLimits 0202 << expectedUsages; 0203 0204 collection = createCollectionChain(QStringLiteral("/INBOX/Foo")); 0205 collection.setRights(Akonadi::Collection::ReadOnly); 0206 0207 capabilities.clear(); 0208 capabilities << QStringLiteral("METADATA") << QStringLiteral("ACL") << QStringLiteral("QUOTA"); 0209 0210 expectedAnnotations.clear(); 0211 expectedAnnotations.insert("/shared/vendor/kolab/folder-test", "true"); 0212 expectedAnnotations.insert("/shared/vendor/kolab/folder-test2", ""); 0213 0214 scenario.clear(); 0215 scenario << defaultPoolConnectionScenario() << "C: A000003 GETMETADATA (DEPTH infinity) \"INBOX/Foo\" (/shared)" 0216 << R"(S: * METADATA "INBOX/Foo" (/shared/vendor/kolab/folder-test "true"))" 0217 << R"(S: * METADATA "INBOX/Foo" (/shared/vendor/kolab/folder-test2 "NIL"))" 0218 << R"(S: * METADATA "INBOX/Foo" (/shared/vendor/cmu/cyrus-imapd/lastupdate "true"))" 0219 << "S: A000003 OK GETMETADATA complete" 0220 << "C: A000004 MYRIGHTS \"INBOX/Foo\"" 0221 << "S: * MYRIGHTS \"INBOX/Foo\" lrswipkxtecda" 0222 << "S: A000004 OK rights retrieved" 0223 << "C: A000005 GETQUOTAROOT \"INBOX/Foo\"" 0224 << "S: * QUOTAROOT INBOX/Foo user/Foo" 0225 << "S: * QUOTA user/Foo ( )" 0226 << "S: A000005 OK quota retrieved" 0227 << "C: A000006 GETACL \"INBOX/Foo\"" 0228 << "S: * ACL INBOX/Foo foo@kde.org lrswipcda" 0229 << "S: A000006 OK acl retrieved"; 0230 0231 callNames.clear(); 0232 callNames << QStringLiteral("collectionAttributesRetrieved"); 0233 0234 rights = Akonadi::Collection::AllRights; 0235 QTest::newRow("METADATA") << collection << capabilities << scenario << callNames << rights << expectedAnnotations << expectedRoots << expectedLimits 0236 << expectedUsages; 0237 0238 collection = createCollectionChain(QStringLiteral("/INBOX/Foo")); 0239 collection.setRights(Akonadi::Collection::ReadOnly); 0240 0241 capabilities.clear(); 0242 expectedAnnotations.clear(); 0243 0244 callNames.clear(); 0245 callNames << QStringLiteral("collectionAttributesRetrieved"); 0246 0247 rights = Akonadi::Collection::ReadOnly; 0248 0249 scenario.clear(); 0250 scenario << defaultPoolConnectionScenario(); 0251 0252 QTest::newRow("no capabilities") << collection << capabilities << scenario << callNames << rights << expectedAnnotations << expectedRoots 0253 << expectedLimits << expectedUsages; 0254 0255 // 0256 // Test for GETQUOTAROOT with multiple IMAP quota roots but only one QUOTA response. 0257 // 0258 collection = createCollectionChain(QStringLiteral("/INBOX")); 0259 collection.setRights(Akonadi::Collection::ReadOnly); 0260 0261 capabilities.clear(); 0262 capabilities << QStringLiteral("QUOTA"); 0263 0264 rights = Akonadi::Collection::ReadOnly; 0265 0266 scenario.clear(); 0267 scenario << defaultPoolConnectionScenario() << "C: A000003 GETQUOTAROOT \"INBOX\"" 0268 << "S: * QUOTAROOT INBOX mailbox1 mailbox2 mailbox3" 0269 << "S: * QUOTA mailbox2 ( STORAGE 21 512 )" 0270 << "S: A000003 OK quota retrieved"; 0271 0272 callNames.clear(); 0273 callNames << QStringLiteral("collectionAttributesRetrieved"); 0274 0275 expectedAnnotations.clear(); 0276 0277 expectedRoots = {"mailbox2"}; 0278 expectedUsages = {{{"STORAGE", 21}}}; 0279 expectedLimits = {{{"STORAGE", 512}}}; 0280 0281 QTest::newRow("multiple quota roots, one resource only") 0282 << collection << capabilities << scenario << callNames << rights << expectedAnnotations << expectedRoots << expectedLimits << expectedUsages; 0283 0284 // 0285 // Test for GETQUOTAROOT with multiple IMAP quota roots, some with no resource, 0286 // but only (not the first on the list) matches the mailbox name. 0287 // 0288 collection = createCollectionChain(QStringLiteral("/INBOX")); 0289 collection.setRights(Akonadi::Collection::ReadOnly); 0290 0291 capabilities.clear(); 0292 capabilities << QStringLiteral("QUOTA"); 0293 0294 rights = Akonadi::Collection::ReadOnly; 0295 0296 scenario.clear(); 0297 scenario << defaultPoolConnectionScenario() << "C: A000003 GETQUOTAROOT \"INBOX\"" 0298 << "S: * QUOTAROOT INBOX mailbox1 INBOX mailbox2 mailbox3" 0299 << "S: * QUOTA mailbox1 ( STORAGE 21 512 )" 0300 << "S: * QUOTA INBOX ( STORAGE 11 250 )" 0301 << "S: * QUOTA mailbox2 ( )" 0302 << "S: * QUOTA mailbox3 ( STORAGE 31 500 )" 0303 << "S: A000003 OK quota retrieved"; 0304 0305 callNames.clear(); 0306 callNames << QStringLiteral("collectionAttributesRetrieved"); 0307 0308 expectedAnnotations.clear(); 0309 0310 expectedRoots = {"mailbox1", "INBOX", "mailbox3"}; 0311 expectedUsages = {{{"STORAGE", 21}}, {{"STORAGE", 11}}, {{"STORAGE", 31}}}; 0312 expectedLimits = {{{"STORAGE", 512}}, {{"STORAGE", 250}}, {{"STORAGE", 500}}}; 0313 0314 QTest::newRow("multiple quota roots, some with no resources, one matches the mailbox name") 0315 << collection << capabilities << scenario << callNames << rights << expectedAnnotations << expectedRoots << expectedLimits << expectedUsages; 0316 } 0317 0318 void shouldCollectionRetrieveMetadata() 0319 { 0320 QFETCH(Akonadi::Collection, collection); 0321 QFETCH(QStringList, capabilities); 0322 QFETCH(QList<QByteArray>, scenario); 0323 QFETCH(QStringList, callNames); 0324 QFETCH(Akonadi::Collection::Rights, expectedRights); 0325 QFETCH(QBYTEARRAYMAP, expectedAnnotations); 0326 QFETCH(QList<QByteArray>, expectedRoots); 0327 QFETCH(QList<QBYTEARRAYINT64MAP>, expectedLimits); 0328 QFETCH(QList<QBYTEARRAYINT64MAP>, expectedUsages); 0329 0330 FakeServer server; 0331 server.setScenario(scenario); 0332 server.startAndWait(); 0333 0334 SessionPool pool(1); 0335 0336 pool.setPasswordRequester(createDefaultRequester()); 0337 QVERIFY(pool.connect(createDefaultAccount())); 0338 QVERIFY(waitForSignal(&pool, SIGNAL(connectDone(int, QString)))); 0339 0340 DummyResourceState::Ptr state = DummyResourceState::Ptr(new DummyResourceState); 0341 state->setCollection(collection); 0342 state->setServerCapabilities(capabilities); 0343 state->setUserName(QStringLiteral("Hans")); 0344 auto task = new RetrieveCollectionMetadataTask(state); 0345 0346 task->start(&pool); 0347 0348 QTRY_COMPARE(state->calls().count(), callNames.size()); 0349 for (int i = 0; i < callNames.size(); i++) { 0350 QString command = QString::fromUtf8(state->calls().at(i).first); 0351 QVariant parameter = state->calls().at(i).second; 0352 0353 if (command == QLatin1StringView("cancelTask") && callNames[i] != QLatin1StringView("cancelTask")) { 0354 qDebug() << "Got a cancel:" << parameter.toString(); 0355 } 0356 0357 QCOMPARE(command, callNames[i]); 0358 0359 if (command == QLatin1StringView("cancelTask")) { 0360 QVERIFY(!parameter.toString().isEmpty()); 0361 } 0362 0363 if (command == QLatin1StringView("collectionAttributesRetrieved")) { 0364 auto collection = parameter.value<Akonadi::Collection>(); 0365 QCOMPARE(collection.rights(), expectedRights); 0366 0367 if (!expectedAnnotations.isEmpty()) { 0368 QVERIFY(collection.hasAttribute<Akonadi::CollectionAnnotationsAttribute>()); 0369 QCOMPARE(collection.attribute<Akonadi::CollectionAnnotationsAttribute>()->annotations(), expectedAnnotations); 0370 } 0371 0372 if (!expectedRoots.isEmpty()) { 0373 int index = expectedRoots.indexOf("INBOX"); 0374 0375 QVERIFY(collection.hasAttribute<Akonadi::ImapQuotaAttribute>()); 0376 QCOMPARE(collection.attribute<Akonadi::ImapQuotaAttribute>()->roots(), expectedRoots); 0377 QCOMPARE(collection.attribute<Akonadi::ImapQuotaAttribute>()->limits(), expectedLimits); 0378 QCOMPARE(collection.attribute<Akonadi::ImapQuotaAttribute>()->usages(), expectedUsages); 0379 0380 QVERIFY(collection.hasAttribute<Akonadi::CollectionQuotaAttribute>()); 0381 if (index != -1) { 0382 QCOMPARE(collection.attribute<Akonadi::CollectionQuotaAttribute>()->currentValue(), expectedUsages.at(index)["STORAGE"] * 1024); 0383 QCOMPARE(collection.attribute<Akonadi::CollectionQuotaAttribute>()->maximumValue(), expectedLimits.at(index)["STORAGE"] * 1024); 0384 } else { 0385 QCOMPARE(collection.attribute<Akonadi::CollectionQuotaAttribute>()->currentValue(), expectedUsages.first()["STORAGE"] * 1024); 0386 QCOMPARE(collection.attribute<Akonadi::CollectionQuotaAttribute>()->maximumValue(), expectedLimits.first()["STORAGE"] * 1024); 0387 } 0388 } 0389 } 0390 } 0391 0392 QVERIFY(server.isAllScenarioDone()); 0393 0394 server.quit(); 0395 } 0396 }; 0397 0398 QTEST_GUILESS_MAIN(TestRetrieveCollectionMetadataTask) 0399 0400 #include "testretrievecollectionmetadatatask.moc"