File indexing completed on 2024-11-10 04:40:16

0001 /*
0002     SPDX-FileCopyrightText: 2014 Christian Mollekopf <mollekopf@kolabsys.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <QObject>
0008 
0009 #include "attributefactory.h"
0010 #include "control.h"
0011 #include "item.h"
0012 #include "itemcreatejob.h"
0013 #include "itemfetchjob.h"
0014 #include "itemfetchscope.h"
0015 #include "itemmodifyjob.h"
0016 #include "monitor.h"
0017 #include "qtest_akonadi.h"
0018 #include "resourceselectjob_p.h"
0019 #include "tagattribute.h"
0020 #include "tagcreatejob.h"
0021 #include "tagdeletejob.h"
0022 #include "tagfetchjob.h"
0023 #include "tagfetchscope.h"
0024 #include "tagmodifyjob.h"
0025 
0026 using namespace Akonadi;
0027 
0028 class TagTest : public QObject
0029 {
0030     Q_OBJECT
0031 
0032 private Q_SLOTS:
0033     void initTestCase();
0034 
0035     void testTag();
0036     void testCreateFetch();
0037     void testRID();
0038     void testDelete();
0039     void testDeleteRIDIsolation();
0040     void testModify();
0041     void testModifyFromResource();
0042     void testCreateMerge();
0043     void testAttributes();
0044     void testTagItem();
0045     void testCreateItem();
0046     void testCreateItemWithTags();
0047     void testRIDIsolation();
0048     void testFetchTagIdWithItem();
0049     void testFetchFullTagWithItem();
0050     void testModifyItemWithTagByGID();
0051     void testModifyItemWithTagByRID();
0052     void testMonitor();
0053     void testTagAttributeConfusionBug();
0054     void testFetchItemsByTag();
0055     void tagModifyJobShouldOnlySendModifiedAttributes();
0056 };
0057 
0058 void TagTest::initTestCase()
0059 {
0060     AkonadiTest::checkTestIsIsolated();
0061     AkonadiTest::setAllResourcesOffline();
0062     AttributeFactory::registerAttribute<TagAttribute>();
0063     qRegisterMetaType<Akonadi::Tag>();
0064     qRegisterMetaType<QSet<Akonadi::Tag>>();
0065     qRegisterMetaType<Akonadi::Item::List>();
0066 
0067     // Delete the default Knut tag - it's interfering with this test
0068     auto fetchJob = new TagFetchJob(this);
0069     AKVERIFYEXEC(fetchJob);
0070     QCOMPARE(fetchJob->tags().size(), 1);
0071     auto deleteJob = new TagDeleteJob(fetchJob->tags().first(), this);
0072     AKVERIFYEXEC(deleteJob);
0073 }
0074 
0075 void TagTest::testTag()
0076 {
0077     Tag tag1;
0078     Tag tag2;
0079 
0080     // Invalid tags are equal
0081     QVERIFY(tag1 == tag2);
0082 
0083     // Invalid tags with different GIDs are not equal
0084     tag1.setGid("GID1");
0085     QVERIFY(tag1 != tag2);
0086     tag2.setGid("GID2");
0087     QVERIFY(tag1 != tag2);
0088 
0089     // Invalid tags with equal GIDs are equal
0090     tag1.setGid("GID2");
0091     QVERIFY(tag1 == tag2);
0092 
0093     // Valid tags with different IDs are not equal
0094     tag1 = Tag(1);
0095     tag2 = Tag(2);
0096     QVERIFY(tag1 != tag2);
0097 
0098     // Valid tags with different IDs and equal GIDs are still not equal
0099     tag1.setGid("GID1");
0100     tag2.setGid("GID1");
0101     QVERIFY(tag1 != tag2);
0102 
0103     // Valid tags with equal ID are equal regardless of GIDs
0104     tag2 = Tag(1);
0105     tag2.setGid("GID2");
0106     QVERIFY(tag1 == tag2);
0107 }
0108 
0109 void TagTest::testCreateFetch()
0110 {
0111     Tag tag;
0112     tag.setGid("gid");
0113     tag.setType("mytype");
0114     auto createjob = new TagCreateJob(tag, this);
0115     AKVERIFYEXEC(createjob);
0116     QVERIFY(createjob->tag().isValid());
0117 
0118     {
0119         auto fetchJob = new TagFetchJob(this);
0120         AKVERIFYEXEC(fetchJob);
0121         QCOMPARE(fetchJob->tags().size(), 1);
0122         QCOMPARE(fetchJob->tags().first().gid(), QByteArray("gid"));
0123         QCOMPARE(fetchJob->tags().first().type(), QByteArray("mytype"));
0124 
0125         auto deleteJob = new TagDeleteJob(fetchJob->tags().first(), this);
0126         AKVERIFYEXEC(deleteJob);
0127     }
0128 
0129     {
0130         auto fetchJob = new TagFetchJob(this);
0131         AKVERIFYEXEC(fetchJob);
0132         QCOMPARE(fetchJob->tags().size(), 0);
0133     }
0134 }
0135 
0136 void TagTest::testRID()
0137 {
0138     {
0139         auto select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_0"));
0140         AKVERIFYEXEC(select);
0141     }
0142     Tag tag;
0143     tag.setGid("gid");
0144     tag.setType("mytype");
0145     tag.setRemoteId("rid");
0146     auto createjob = new TagCreateJob(tag, this);
0147     AKVERIFYEXEC(createjob);
0148     QVERIFY(createjob->tag().isValid());
0149 
0150     {
0151         auto fetchJob = new TagFetchJob(this);
0152         fetchJob->fetchScope().setFetchRemoteId(true);
0153         AKVERIFYEXEC(fetchJob);
0154         QCOMPARE(fetchJob->tags().size(), 1);
0155         QCOMPARE(fetchJob->tags().first().gid(), QByteArray("gid"));
0156         QCOMPARE(fetchJob->tags().first().type(), QByteArray("mytype"));
0157         QCOMPARE(fetchJob->tags().first().remoteId(), QByteArray("rid"));
0158 
0159         auto deleteJob = new TagDeleteJob(fetchJob->tags().first(), this);
0160         AKVERIFYEXEC(deleteJob);
0161     }
0162     {
0163         auto select = new ResourceSelectJob(QStringLiteral(""));
0164         AKVERIFYEXEC(select);
0165     }
0166 }
0167 
0168 void TagTest::testRIDIsolation()
0169 {
0170     {
0171         auto select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_0"));
0172         AKVERIFYEXEC(select);
0173     }
0174 
0175     Tag tag;
0176     tag.setGid("gid");
0177     tag.setType("mytype");
0178     tag.setRemoteId("rid_0");
0179 
0180     auto createJob = new TagCreateJob(tag, this);
0181     AKVERIFYEXEC(createJob);
0182     QVERIFY(createJob->tag().isValid());
0183 
0184     qint64 tagId;
0185     {
0186         auto fetchJob = new TagFetchJob(this);
0187         fetchJob->fetchScope().setFetchRemoteId(true);
0188         AKVERIFYEXEC(fetchJob);
0189         QCOMPARE(fetchJob->tags().count(), 1);
0190         QCOMPARE(fetchJob->tags().first().gid(), QByteArray("gid"));
0191         QCOMPARE(fetchJob->tags().first().type(), QByteArray("mytype"));
0192         QCOMPARE(fetchJob->tags().first().remoteId(), QByteArray("rid_0"));
0193         tagId = fetchJob->tags().first().id();
0194     }
0195 
0196     {
0197         auto select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_1"));
0198         AKVERIFYEXEC(select);
0199     }
0200 
0201     tag.setRemoteId("rid_1");
0202     createJob = new TagCreateJob(tag, this);
0203     createJob->setMergeIfExisting(true);
0204     AKVERIFYEXEC(createJob);
0205     QVERIFY(createJob->tag().isValid());
0206 
0207     {
0208         auto fetchJob = new TagFetchJob(this);
0209         fetchJob->fetchScope().setFetchRemoteId(true);
0210         AKVERIFYEXEC(fetchJob);
0211         QCOMPARE(fetchJob->tags().count(), 1);
0212         QCOMPARE(fetchJob->tags().first().gid(), QByteArray("gid"));
0213         QCOMPARE(fetchJob->tags().first().type(), QByteArray("mytype"));
0214         QCOMPARE(fetchJob->tags().first().remoteId(), QByteArray("rid_1"));
0215 
0216         QCOMPARE(fetchJob->tags().first().id(), tagId);
0217     }
0218 
0219     auto deleteJob = new TagDeleteJob(Tag(tagId), this);
0220     AKVERIFYEXEC(deleteJob);
0221 
0222     {
0223         auto select = new ResourceSelectJob(QStringLiteral(""));
0224         AKVERIFYEXEC(select);
0225     }
0226 }
0227 
0228 void TagTest::testDelete()
0229 {
0230     Akonadi::Monitor monitor;
0231     monitor.setTypeMonitored(Monitor::Tags);
0232     QSignalSpy spy(&monitor, &Monitor::tagRemoved);
0233 
0234     Tag tag1;
0235     {
0236         tag1.setGid("tag1");
0237         auto createjob = new TagCreateJob(tag1, this);
0238         AKVERIFYEXEC(createjob);
0239         QVERIFY(createjob->tag().isValid());
0240         tag1 = createjob->tag();
0241     }
0242     Tag tag2;
0243     {
0244         tag2.setGid("tag2");
0245         auto createjob = new TagCreateJob(tag2, this);
0246         AKVERIFYEXEC(createjob);
0247         QVERIFY(createjob->tag().isValid());
0248         tag2 = createjob->tag();
0249     }
0250     {
0251         auto deleteJob = new TagDeleteJob(tag1, this);
0252         AKVERIFYEXEC(deleteJob);
0253     }
0254 
0255     {
0256         auto fetchJob = new TagFetchJob(this);
0257         AKVERIFYEXEC(fetchJob);
0258         QCOMPARE(fetchJob->tags().size(), 1);
0259         QCOMPARE(fetchJob->tags().first().gid(), tag2.gid());
0260     }
0261     {
0262         auto deleteJob = new TagDeleteJob(tag2, this);
0263         AKVERIFYEXEC(deleteJob);
0264     }
0265 
0266     // Collect Remove notification, so that they don't interfere with testDeleteRIDIsolation
0267     QTRY_VERIFY(!spy.isEmpty());
0268 }
0269 
0270 void TagTest::testDeleteRIDIsolation()
0271 {
0272     Tag tag;
0273     tag.setGid("gid");
0274     tag.setType("mytype");
0275     tag.setRemoteId("rid_0");
0276 
0277     {
0278         auto select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_0"));
0279         AKVERIFYEXEC(select);
0280 
0281         auto createJob = new TagCreateJob(tag, this);
0282         AKVERIFYEXEC(createJob);
0283         QVERIFY(createJob->tag().isValid());
0284         tag.setId(createJob->tag().id());
0285     }
0286 
0287     tag.setRemoteId("rid_1");
0288     {
0289         auto select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_1"));
0290         AKVERIFYEXEC(select);
0291 
0292         auto createJob = new TagCreateJob(tag, this);
0293         createJob->setMergeIfExisting(true);
0294         AKVERIFYEXEC(createJob);
0295         QVERIFY(createJob->tag().isValid());
0296     }
0297 
0298     auto monitor = AkonadiTest::getTestMonitor();
0299     QSignalSpy signalSpy(monitor.get(), &Monitor::tagRemoved);
0300 
0301     auto deleteJob = new TagDeleteJob(tag, this);
0302     AKVERIFYEXEC(deleteJob);
0303 
0304     // Other tests notifications might interfere due to notification compression on server
0305     QTRY_VERIFY(signalSpy.count() >= 1);
0306 
0307     Tag removedTag;
0308     while (!signalSpy.isEmpty()) {
0309         const Tag t = signalSpy.takeFirst().takeFirst().value<Akonadi::Tag>();
0310         if (t.id() == tag.id()) {
0311             removedTag = t;
0312             break;
0313         }
0314     }
0315 
0316     QVERIFY(removedTag.isValid());
0317     QVERIFY(removedTag.remoteId().isEmpty());
0318 
0319     {
0320         auto select = new ResourceSelectJob(QStringLiteral(""), this);
0321         AKVERIFYEXEC(select);
0322     }
0323 }
0324 
0325 void TagTest::testModify()
0326 {
0327     Tag tag;
0328     {
0329         tag.setGid("gid");
0330         auto createjob = new TagCreateJob(tag, this);
0331         AKVERIFYEXEC(createjob);
0332         QVERIFY(createjob->tag().isValid());
0333         tag = createjob->tag();
0334     }
0335 
0336     // We can add an attribute
0337     {
0338         auto attr = tag.attribute<Akonadi::TagAttribute>(Tag::AddIfMissing);
0339         attr->setDisplayName(QStringLiteral("display name"));
0340         tag.setParent(Tag(0));
0341         tag.setType("mytype");
0342         auto modJob = new TagModifyJob(tag, this);
0343         AKVERIFYEXEC(modJob);
0344 
0345         auto fetchJob = new TagFetchJob(this);
0346         fetchJob->fetchScope().fetchAttribute<Akonadi::TagAttribute>();
0347         AKVERIFYEXEC(fetchJob);
0348         QCOMPARE(fetchJob->tags().size(), 1);
0349         QVERIFY(fetchJob->tags().first().hasAttribute<Akonadi::TagAttribute>());
0350     }
0351     // We can update an attribute
0352     {
0353         auto attr = tag.attribute<Akonadi::TagAttribute>(Tag::AddIfMissing);
0354         attr->setDisplayName(QStringLiteral("display name2"));
0355         auto modJob = new TagModifyJob(tag, this);
0356         AKVERIFYEXEC(modJob);
0357 
0358         auto fetchJob = new TagFetchJob(this);
0359         fetchJob->fetchScope().fetchAttribute<Akonadi::TagAttribute>();
0360         AKVERIFYEXEC(fetchJob);
0361         QCOMPARE(fetchJob->tags().size(), 1);
0362         QVERIFY(fetchJob->tags().first().hasAttribute<Akonadi::TagAttribute>());
0363         QCOMPARE(fetchJob->tags().first().attribute<Akonadi::TagAttribute>()->displayName(), attr->displayName());
0364     }
0365     // We can clear an attribute
0366     {
0367         tag.removeAttribute<Akonadi::TagAttribute>();
0368         auto modJob = new TagModifyJob(tag, this);
0369         AKVERIFYEXEC(modJob);
0370 
0371         auto fetchJob = new TagFetchJob(this);
0372         fetchJob->fetchScope().fetchAttribute<Akonadi::TagAttribute>();
0373         AKVERIFYEXEC(fetchJob);
0374         QCOMPARE(fetchJob->tags().size(), 1);
0375         QVERIFY(!fetchJob->tags().first().hasAttribute<Akonadi::TagAttribute>());
0376     }
0377 
0378     auto deleteJob = new TagDeleteJob(tag, this);
0379     AKVERIFYEXEC(deleteJob);
0380 }
0381 
0382 void TagTest::testModifyFromResource()
0383 {
0384     auto select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_0"));
0385     AKVERIFYEXEC(select);
0386 
0387     Tag tag;
0388     {
0389         tag.setGid("gid");
0390         tag.setRemoteId("rid");
0391         auto createjob = new TagCreateJob(tag, this);
0392         AKVERIFYEXEC(createjob);
0393         QVERIFY(createjob->tag().isValid());
0394         tag = createjob->tag();
0395     }
0396 
0397     {
0398         tag.setRemoteId(QByteArray(""));
0399         auto modJob = new TagModifyJob(tag, this);
0400         AKVERIFYEXEC(modJob);
0401 
0402         // The tag is removed on the server, because we just removed the last
0403         // RemoteID
0404         auto fetchJob = new TagFetchJob(this);
0405         AKVERIFYEXEC(fetchJob);
0406         QCOMPARE(fetchJob->tags().size(), 0);
0407     }
0408 }
0409 
0410 void TagTest::testCreateMerge()
0411 {
0412     Tag tag;
0413     {
0414         tag.setGid("gid");
0415         auto createjob = new TagCreateJob(tag, this);
0416         AKVERIFYEXEC(createjob);
0417         QVERIFY(createjob->tag().isValid());
0418         tag = createjob->tag();
0419     }
0420     {
0421         Tag tag2;
0422         tag2.setGid("gid");
0423         auto createjob = new TagCreateJob(tag2, this);
0424         createjob->setMergeIfExisting(true);
0425         AKVERIFYEXEC(createjob);
0426         QVERIFY(createjob->tag().isValid());
0427         QCOMPARE(createjob->tag().id(), tag.id());
0428     }
0429 
0430     auto deleteJob = new TagDeleteJob(tag, this);
0431     AKVERIFYEXEC(deleteJob);
0432 }
0433 
0434 void TagTest::testAttributes()
0435 {
0436     Tag tag;
0437     {
0438         tag.setGid("gid2");
0439         auto attr = tag.attribute<TagAttribute>(Tag::AddIfMissing);
0440         attr->setDisplayName(QStringLiteral("name"));
0441         attr->setInToolbar(true);
0442         auto createjob = new TagCreateJob(tag, this);
0443         AKVERIFYEXEC(createjob);
0444         QVERIFY(createjob->tag().isValid());
0445         tag = createjob->tag();
0446 
0447         {
0448             auto fetchJob = new TagFetchJob(createjob->tag(), this);
0449             fetchJob->fetchScope().fetchAttribute<TagAttribute>();
0450             AKVERIFYEXEC(fetchJob);
0451             QCOMPARE(fetchJob->tags().size(), 1);
0452             QVERIFY(fetchJob->tags().first().hasAttribute<TagAttribute>());
0453             // we need to clone because the returned attribute is just a reference and destroyed on the next line
0454             // FIXME we should find a better solution for this (like returning a smart pointer or value object)
0455             QScopedPointer<TagAttribute> tagAttr(fetchJob->tags().first().attribute<TagAttribute>()->clone());
0456             QVERIFY(tagAttr);
0457             QCOMPARE(tagAttr->displayName(), QStringLiteral("name"));
0458             QCOMPARE(tagAttr->inToolbar(), true);
0459         }
0460     }
0461     // Try fetching multiple items
0462     Tag tag2;
0463     {
0464         tag2.setGid("gid22");
0465         TagAttribute *attr = tag.attribute<TagAttribute>(Tag::AddIfMissing)->clone();
0466         attr->setDisplayName(QStringLiteral("name2"));
0467         attr->setInToolbar(true);
0468         tag2.addAttribute(attr);
0469         auto createjob = new TagCreateJob(tag2, this);
0470         AKVERIFYEXEC(createjob);
0471         QVERIFY(createjob->tag().isValid());
0472         tag2 = createjob->tag();
0473 
0474         {
0475             auto fetchJob = new TagFetchJob(Tag::List() << tag << tag2, this);
0476             fetchJob->fetchScope().fetchAttribute<TagAttribute>();
0477             AKVERIFYEXEC(fetchJob);
0478             QCOMPARE(fetchJob->tags().size(), 2);
0479             QVERIFY(fetchJob->tags().at(0).hasAttribute<TagAttribute>());
0480             QVERIFY(fetchJob->tags().at(1).hasAttribute<TagAttribute>());
0481         }
0482     }
0483 
0484     auto deleteJob = new TagDeleteJob(Tag::List() << tag << tag2, this);
0485     AKVERIFYEXEC(deleteJob);
0486 }
0487 
0488 void TagTest::testTagItem()
0489 {
0490     Akonadi::Monitor monitor;
0491     monitor.itemFetchScope().setFetchTags(true);
0492     monitor.setAllMonitored(true);
0493     QVERIFY(AkonadiTest::akWaitForSignal(&monitor, &Monitor::monitorReady));
0494 
0495     const Collection res3 = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res3")));
0496     Tag tag;
0497     {
0498         auto createjob = new TagCreateJob(Tag(QStringLiteral("gid1")), this);
0499         AKVERIFYEXEC(createjob);
0500         tag = createjob->tag();
0501     }
0502 
0503     Item item1;
0504     {
0505         item1.setMimeType(QStringLiteral("application/octet-stream"));
0506         auto append = new ItemCreateJob(item1, res3, this);
0507         AKVERIFYEXEC(append);
0508         item1 = append->item();
0509     }
0510 
0511     item1.setTag(tag);
0512 
0513     QSignalSpy tagsSpy(&monitor, &Monitor::itemsTagsChanged);
0514 
0515     auto modJob = new ItemModifyJob(item1, this);
0516     AKVERIFYEXEC(modJob);
0517 
0518     QTRY_VERIFY(tagsSpy.count() >= 1);
0519     QTRY_COMPARE(tagsSpy.last().first().value<Akonadi::Item::List>().first().id(), item1.id());
0520     QTRY_COMPARE(tagsSpy.last().at(1).value<QSet<Tag>>().size(), 1); // 1 added tag
0521 
0522     auto fetchJob = new ItemFetchJob(item1, this);
0523     fetchJob->fetchScope().setFetchTags(true);
0524     AKVERIFYEXEC(fetchJob);
0525     QCOMPARE(fetchJob->items().first().tags().size(), 1);
0526 
0527     auto deleteJob = new TagDeleteJob(tag, this);
0528     AKVERIFYEXEC(deleteJob);
0529 }
0530 
0531 void TagTest::testCreateItem()
0532 {
0533     const Collection res3 = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res3")));
0534     Tag tag;
0535     {
0536         auto createjob = new TagCreateJob(Tag(QStringLiteral("gid1")), this);
0537         AKVERIFYEXEC(createjob);
0538         tag = createjob->tag();
0539     }
0540 
0541     Item item1;
0542     {
0543         item1.setMimeType(QStringLiteral("application/octet-stream"));
0544         item1.setTag(tag);
0545         auto append = new ItemCreateJob(item1, res3, this);
0546         AKVERIFYEXEC(append);
0547         item1 = append->item();
0548     }
0549 
0550     auto fetchJob = new ItemFetchJob(item1, this);
0551     fetchJob->fetchScope().setFetchTags(true);
0552     AKVERIFYEXEC(fetchJob);
0553     QCOMPARE(fetchJob->items().first().tags().size(), 1);
0554 
0555     auto deleteJob = new TagDeleteJob(tag, this);
0556     AKVERIFYEXEC(deleteJob);
0557 }
0558 
0559 void TagTest::testCreateItemWithTags()
0560 {
0561     const Collection res3 = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res3")));
0562     Tag tag1;
0563     {
0564         auto createjob = new TagCreateJob(Tag(QStringLiteral("gid1")), this);
0565         AKVERIFYEXEC(createjob);
0566         tag1 = createjob->tag();
0567     }
0568     Tag tag2;
0569     {
0570         auto createjob = new TagCreateJob(Tag(QStringLiteral("gid2")), this);
0571         AKVERIFYEXEC(createjob);
0572         tag2 = createjob->tag();
0573     }
0574 
0575     Item item1;
0576     {
0577         item1.setMimeType(QStringLiteral("application/octet-stream"));
0578         item1.setTags({tag1, tag2});
0579         auto append = new ItemCreateJob(item1, res3, this);
0580         AKVERIFYEXEC(append);
0581         item1 = append->item();
0582     }
0583 
0584     auto fetchJob = new ItemFetchJob(item1, this);
0585     fetchJob->fetchScope().setFetchTags(true);
0586     AKVERIFYEXEC(fetchJob);
0587     auto fetchTags = fetchJob->items().first().tags();
0588 
0589     std::unique_ptr<TagDeleteJob, void (*)(TagDeleteJob *)> finally(new TagDeleteJob({tag1, tag2}, this), [](TagDeleteJob *j) {
0590         j->exec();
0591         delete j;
0592     });
0593     QCOMPARE(fetchTags.size(), 2);
0594     QVERIFY(fetchTags.contains(tag1));
0595     QVERIFY(fetchTags.contains(tag2));
0596 }
0597 
0598 void TagTest::testFetchTagIdWithItem()
0599 {
0600     const Collection res3 = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res3")));
0601     Tag tag;
0602     {
0603         auto createjob = new TagCreateJob(Tag(QStringLiteral("gid1")), this);
0604         AKVERIFYEXEC(createjob);
0605         tag = createjob->tag();
0606     }
0607 
0608     Item item1;
0609     {
0610         item1.setMimeType(QStringLiteral("application/octet-stream"));
0611         item1.setTag(tag);
0612         auto append = new ItemCreateJob(item1, res3, this);
0613         AKVERIFYEXEC(append);
0614         item1 = append->item();
0615     }
0616 
0617     auto fetchJob = new ItemFetchJob(item1, this);
0618     fetchJob->fetchScope().setFetchTags(true);
0619     fetchJob->fetchScope().tagFetchScope().setFetchIdOnly(true);
0620     AKVERIFYEXEC(fetchJob);
0621     QCOMPARE(fetchJob->items().first().tags().size(), 1);
0622     Tag t = fetchJob->items().first().tags().first();
0623     QCOMPARE(t.id(), tag.id());
0624     QVERIFY(t.gid().isEmpty());
0625 
0626     auto deleteJob = new TagDeleteJob(tag, this);
0627     AKVERIFYEXEC(deleteJob);
0628 }
0629 
0630 void TagTest::testFetchFullTagWithItem()
0631 {
0632     const Collection res3 = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res3")));
0633     Tag tag;
0634     {
0635         auto createjob = new TagCreateJob(Tag(QStringLiteral("gid1")), this);
0636         AKVERIFYEXEC(createjob);
0637         tag = createjob->tag();
0638     }
0639 
0640     Item item1;
0641     {
0642         item1.setMimeType(QStringLiteral("application/octet-stream"));
0643         auto append = new ItemCreateJob(item1, res3, this);
0644         AKVERIFYEXEC(append);
0645         item1 = append->item();
0646         // FIXME This should also be possible with create, but isn't
0647         item1.setTag(tag);
0648     }
0649 
0650     auto modJob = new ItemModifyJob(item1, this);
0651     AKVERIFYEXEC(modJob);
0652 
0653     auto fetchJob = new ItemFetchJob(item1, this);
0654     fetchJob->fetchScope().setFetchTags(true);
0655     fetchJob->fetchScope().tagFetchScope().setFetchIdOnly(false);
0656     AKVERIFYEXEC(fetchJob);
0657     QCOMPARE(fetchJob->items().first().tags().size(), 1);
0658     Tag t = fetchJob->items().first().tags().first();
0659     QCOMPARE(t, tag);
0660     QVERIFY(!t.gid().isEmpty());
0661 
0662     auto deleteJob = new TagDeleteJob(tag, this);
0663     AKVERIFYEXEC(deleteJob);
0664 }
0665 
0666 void TagTest::testModifyItemWithTagByGID()
0667 {
0668     const Collection res3 = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res3")));
0669     {
0670         Tag tag;
0671         tag.setGid("gid2");
0672         auto createjob = new TagCreateJob(tag, this);
0673         AKVERIFYEXEC(createjob);
0674     }
0675 
0676     Item item1;
0677     {
0678         item1.setMimeType(QStringLiteral("application/octet-stream"));
0679         auto append = new ItemCreateJob(item1, res3, this);
0680         AKVERIFYEXEC(append);
0681         item1 = append->item();
0682     }
0683 
0684     Tag tag;
0685     tag.setGid("gid2");
0686     item1.setTag(tag);
0687 
0688     auto modJob = new ItemModifyJob(item1, this);
0689     AKVERIFYEXEC(modJob);
0690 
0691     auto fetchJob = new ItemFetchJob(item1, this);
0692     fetchJob->fetchScope().setFetchTags(true);
0693     AKVERIFYEXEC(fetchJob);
0694     QCOMPARE(fetchJob->items().first().tags().size(), 1);
0695 
0696     auto deleteJob = new TagDeleteJob(fetchJob->items().first().tags().first(), this);
0697     AKVERIFYEXEC(deleteJob);
0698 }
0699 
0700 void TagTest::testModifyItemWithTagByRID()
0701 {
0702     {
0703         auto select = new ResourceSelectJob(QStringLiteral("akonadi_knut_resource_0"));
0704         AKVERIFYEXEC(select);
0705     }
0706 
0707     const Collection res3 = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res3")));
0708     Tag tag3;
0709     {
0710         tag3.setGid("gid3");
0711         tag3.setRemoteId("rid3");
0712         auto createjob = new TagCreateJob(tag3, this);
0713         AKVERIFYEXEC(createjob);
0714         tag3 = createjob->tag();
0715     }
0716 
0717     Item item1;
0718     {
0719         item1.setMimeType(QStringLiteral("application/octet-stream"));
0720         auto append = new ItemCreateJob(item1, res3, this);
0721         AKVERIFYEXEC(append);
0722         item1 = append->item();
0723     }
0724 
0725     Tag tag;
0726     tag.setRemoteId("rid2");
0727     item1.setTag(tag);
0728 
0729     auto modJob = new ItemModifyJob(item1, this);
0730     AKVERIFYEXEC(modJob);
0731 
0732     auto fetchJob = new ItemFetchJob(item1, this);
0733     fetchJob->fetchScope().setFetchTags(true);
0734     AKVERIFYEXEC(fetchJob);
0735     QCOMPARE(fetchJob->items().first().tags().size(), 1);
0736 
0737     {
0738         auto deleteJob = new TagDeleteJob(fetchJob->items().first().tags().first(), this);
0739         AKVERIFYEXEC(deleteJob);
0740     }
0741 
0742     {
0743         auto deleteJob = new TagDeleteJob(tag3, this);
0744         AKVERIFYEXEC(deleteJob);
0745     }
0746 
0747     {
0748         auto select = new ResourceSelectJob(QStringLiteral(""));
0749         AKVERIFYEXEC(select);
0750     }
0751 }
0752 
0753 void TagTest::testMonitor()
0754 {
0755     Akonadi::Monitor monitor;
0756     monitor.setTypeMonitored(Akonadi::Monitor::Tags);
0757     monitor.tagFetchScope().fetchAttribute<Akonadi::TagAttribute>();
0758     QVERIFY(AkonadiTest::akWaitForSignal(&monitor, &Monitor::monitorReady));
0759 
0760     Akonadi::Tag createdTag;
0761     {
0762         QSignalSpy addedSpy(&monitor, &Monitor::tagAdded);
0763         QVERIFY(addedSpy.isValid());
0764         Tag tag;
0765         tag.setGid("gid2");
0766         tag.setName(QStringLiteral("name2"));
0767         tag.setType("type2");
0768         auto createjob = new TagCreateJob(tag, this);
0769         AKVERIFYEXEC(createjob);
0770         createdTag = createjob->tag();
0771         QCOMPARE(createdTag.type(), tag.type());
0772         QCOMPARE(createdTag.name(), tag.name());
0773         QCOMPARE(createdTag.gid(), tag.gid());
0774         // We usually pick up signals from the previous tests as well (due to server-side notification caching)
0775         QTRY_VERIFY(addedSpy.count() >= 1);
0776         QTRY_COMPARE(addedSpy.last().first().value<Akonadi::Tag>().id(), createdTag.id());
0777         const auto notifiedTag = addedSpy.last().first().value<Akonadi::Tag>();
0778         QCOMPARE(notifiedTag.type(), createdTag.type());
0779         QCOMPARE(notifiedTag.gid(), createdTag.gid());
0780         QVERIFY(notifiedTag.hasAttribute<Akonadi::TagAttribute>());
0781         QCOMPARE(notifiedTag.name(), createdTag.name()); // requires the TagAttribute
0782     }
0783 
0784     {
0785         QSignalSpy modifiedSpy(&monitor, &Monitor::tagChanged);
0786         QVERIFY(modifiedSpy.isValid());
0787         createdTag.setName(QStringLiteral("name3"));
0788 
0789         auto modJob = new TagModifyJob(createdTag, this);
0790         AKVERIFYEXEC(modJob);
0791         // We usually pick up signals from the previous tests as well (due to server-side notification caching)
0792         QTRY_VERIFY(modifiedSpy.count() >= 1);
0793         QTRY_COMPARE(modifiedSpy.last().first().value<Akonadi::Tag>().id(), createdTag.id());
0794         const auto notifiedTag = modifiedSpy.last().first().value<Akonadi::Tag>();
0795         QCOMPARE(notifiedTag.type(), createdTag.type());
0796         QCOMPARE(notifiedTag.gid(), createdTag.gid());
0797         QVERIFY(notifiedTag.hasAttribute<Akonadi::TagAttribute>());
0798         QCOMPARE(notifiedTag.name(), createdTag.name()); // requires the TagAttribute
0799     }
0800 
0801     {
0802         QSignalSpy removedSpy(&monitor, &Monitor::tagRemoved);
0803         QVERIFY(removedSpy.isValid());
0804         auto deletejob = new TagDeleteJob(createdTag, this);
0805         AKVERIFYEXEC(deletejob);
0806         QTRY_VERIFY(removedSpy.count() >= 1);
0807         QTRY_COMPARE(removedSpy.last().first().value<Akonadi::Tag>().id(), createdTag.id());
0808         const auto notifiedTag = removedSpy.last().first().value<Akonadi::Tag>();
0809         QCOMPARE(notifiedTag.type(), createdTag.type());
0810         QCOMPARE(notifiedTag.gid(), createdTag.gid());
0811         QVERIFY(notifiedTag.hasAttribute<Akonadi::TagAttribute>());
0812         QCOMPARE(notifiedTag.name(), createdTag.name()); // requires the TagAttribute
0813     }
0814 }
0815 
0816 void TagTest::testTagAttributeConfusionBug()
0817 {
0818     // Create two tags
0819     Tag firstTag;
0820     {
0821         firstTag.setGid("gid");
0822         firstTag.setName(QStringLiteral("display name"));
0823         auto createjob = new TagCreateJob(firstTag, this);
0824         AKVERIFYEXEC(createjob);
0825         QVERIFY(createjob->tag().isValid());
0826         firstTag = createjob->tag();
0827     }
0828     Tag secondTag;
0829     {
0830         secondTag.setGid("AnotherGID");
0831         secondTag.setName(QStringLiteral("another name"));
0832         auto createjob = new TagCreateJob(secondTag, this);
0833         AKVERIFYEXEC(createjob);
0834         QVERIFY(createjob->tag().isValid());
0835         secondTag = createjob->tag();
0836     }
0837 
0838     Akonadi::Monitor monitor;
0839     monitor.setTypeMonitored(Akonadi::Monitor::Tags);
0840     QVERIFY(AkonadiTest::akWaitForSignal(&monitor, &Monitor::monitorReady));
0841 
0842     const QList<Tag::Id> firstTagIdList{firstTag.id()};
0843 
0844     // Modify attribute on the first tag
0845     // and check the notification
0846     {
0847         QSignalSpy modifiedSpy(&monitor, &Akonadi::Monitor::tagChanged);
0848 
0849         firstTag.setName(QStringLiteral("renamed"));
0850         auto modJob = new TagModifyJob(firstTag, this);
0851         AKVERIFYEXEC(modJob);
0852 
0853         auto fetchJob = new TagFetchJob(firstTagIdList, this);
0854         QVERIFY(fetchJob->fetchScope().fetchAllAttributes());
0855         AKVERIFYEXEC(fetchJob);
0856         QCOMPARE(fetchJob->tags().size(), 1);
0857         QCOMPARE(fetchJob->tags().first().name(), firstTag.name());
0858 
0859         QTRY_VERIFY(modifiedSpy.count() >= 1);
0860         QTRY_COMPARE(modifiedSpy.last().first().value<Akonadi::Tag>().id(), firstTag.id());
0861         const auto notifiedTag = modifiedSpy.last().first().value<Akonadi::Tag>();
0862         QCOMPARE(notifiedTag.name(), firstTag.name());
0863     }
0864 
0865     // Cleanup
0866     auto deleteJob = new TagDeleteJob(firstTag, this);
0867     AKVERIFYEXEC(deleteJob);
0868     auto anotherDeleteJob = new TagDeleteJob(secondTag, this);
0869     AKVERIFYEXEC(anotherDeleteJob);
0870 }
0871 
0872 void TagTest::testFetchItemsByTag()
0873 {
0874     const Collection res3 = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res3")));
0875     Tag tag;
0876     {
0877         auto createjob = new TagCreateJob(Tag(QStringLiteral("gid1")), this);
0878         AKVERIFYEXEC(createjob);
0879         tag = createjob->tag();
0880     }
0881 
0882     Item item1;
0883     {
0884         item1.setMimeType(QStringLiteral("application/octet-stream"));
0885         auto append = new ItemCreateJob(item1, res3, this);
0886         AKVERIFYEXEC(append);
0887         item1 = append->item();
0888         // FIXME This should also be possible with create, but isn't
0889         item1.setTag(tag);
0890     }
0891 
0892     auto modJob = new ItemModifyJob(item1, this);
0893     AKVERIFYEXEC(modJob);
0894 
0895     auto fetchJob = new ItemFetchJob(tag, this);
0896     AKVERIFYEXEC(fetchJob);
0897     QCOMPARE(fetchJob->items().size(), 1);
0898     Item i = fetchJob->items().first();
0899     QCOMPARE(i, item1);
0900 
0901     auto deleteJob = new TagDeleteJob(tag, this);
0902     AKVERIFYEXEC(deleteJob);
0903 }
0904 
0905 void TagTest::tagModifyJobShouldOnlySendModifiedAttributes()
0906 {
0907     // Given a tag with an attribute
0908     Tag tag(QStringLiteral("tagWithAttr"));
0909     auto attr = new Akonadi::TagAttribute;
0910     attr->setDisplayName(QStringLiteral("display name"));
0911     tag.addAttribute(attr);
0912     {
0913         auto createjob = new TagCreateJob(tag, this);
0914         AKVERIFYEXEC(createjob);
0915         tag = createjob->tag();
0916     }
0917 
0918     // When one job modifies this attribute, and another one does an unrelated modify job
0919     Tag attrModTag(tag.id());
0920     auto modAttr = attrModTag.attribute<Akonadi::TagAttribute>(Tag::AddIfMissing);
0921     modAttr->setDisplayName(QStringLiteral("modified"));
0922     auto attrModJob = new TagModifyJob(attrModTag, this);
0923     AKVERIFYEXEC(attrModJob);
0924 
0925     tag.setType(Tag::GENERIC);
0926     // this job shouldn't send the old attribute again
0927     auto modJob = new TagModifyJob(tag, this);
0928     AKVERIFYEXEC(modJob);
0929 
0930     // Then the tag should have both the modified attribute and the modified type
0931     {
0932         auto fetchJob = new TagFetchJob(this);
0933         fetchJob->fetchScope().fetchAttribute<Akonadi::TagAttribute>();
0934         AKVERIFYEXEC(fetchJob);
0935         QCOMPARE(fetchJob->tags().size(), 1);
0936         const Tag fetchedTag = fetchJob->tags().at(0);
0937         QVERIFY(fetchedTag.hasAttribute<Akonadi::TagAttribute>());
0938         QCOMPARE(fetchedTag.attribute<Akonadi::TagAttribute>()->displayName(), QStringLiteral("modified"));
0939         QCOMPARE(fetchedTag.type(), Tag::GENERIC);
0940     }
0941 
0942     // And when adding a new attribute next to the old one
0943     auto attr2 = AttributeFactory::createAttribute("SecondType");
0944     tag.addAttribute(attr2);
0945     // this job shouldn't send the old attribute again
0946     auto modJob2 = new TagModifyJob(tag, this);
0947     AKVERIFYEXEC(modJob2);
0948 
0949     // Then the tag should have the modified attribute and the second one
0950     {
0951         auto fetchJob = new TagFetchJob(this);
0952         fetchJob->fetchScope().setFetchAllAttributes(true);
0953         AKVERIFYEXEC(fetchJob);
0954         QCOMPARE(fetchJob->tags().size(), 1);
0955         const Tag fetchedTag = fetchJob->tags().at(0);
0956         QVERIFY(fetchedTag.hasAttribute<Akonadi::TagAttribute>());
0957         QCOMPARE(fetchedTag.attribute<Akonadi::TagAttribute>()->displayName(), QStringLiteral("modified"));
0958         QCOMPARE(fetchedTag.type(), Tag::GENERIC);
0959         QVERIFY(fetchedTag.attribute("SecondType"));
0960     }
0961 }
0962 
0963 #include "tagtest.moc"
0964 
0965 QTEST_AKONADIMAIN(TagTest)