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

0001 /*
0002     SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "changerecorder.h"
0008 #include "agentmanager.h"
0009 #include "itemdeletejob.h"
0010 #include "itemfetchscope.h"
0011 #include "itemmodifyjob.h"
0012 #include "testattribute.h"
0013 
0014 #include "qtest_akonadi.h"
0015 
0016 #include <QObject>
0017 #include <QSettings>
0018 
0019 using namespace Akonadi;
0020 
0021 Q_DECLARE_METATYPE(QSet<QByteArray>)
0022 
0023 class ChangeRecorderTest : public QObject
0024 {
0025     Q_OBJECT
0026 
0027 private Q_SLOTS:
0028     void initTestCase()
0029     {
0030         qRegisterMetaType<Akonadi::Item>();
0031         qRegisterMetaType<QSet<QByteArray>>();
0032         AkonadiTest::checkTestIsIsolated();
0033         AkonadiTest::setAllResourcesOffline();
0034 
0035         settings = new QSettings(QStringLiteral("kde.org"), QStringLiteral("akonadi-changerecordertest"), this);
0036     }
0037 
0038     // After each test
0039     void cleanup()
0040     {
0041         // See ChangeRecorderPrivate::notificationsFileName()
0042         QFile::remove(settings->fileName() + QStringLiteral("_changes.dat"));
0043     }
0044 
0045     void testChangeRecorder_data()
0046     {
0047         QTest::addColumn<QStringList>("actions");
0048 
0049         QTest::newRow("nothingToReplay") << (QStringList() << QStringLiteral("rn"));
0050         QTest::newRow("nothingOneNothing") << (QStringList() << QStringLiteral("rn") << QStringLiteral("c2") << QStringLiteral("r2") << QStringLiteral("rn"));
0051         QTest::newRow("multipleItems") << (QStringList() << QStringLiteral("c1") << QStringLiteral("c2") << QStringLiteral("c3") << QStringLiteral("r1")
0052                                                          << QStringLiteral("c4") << QStringLiteral("r2") << QStringLiteral("r3") << QStringLiteral("r4")
0053                                                          << QStringLiteral("rn"));
0054         QTest::newRow("reload") << (QStringList() << QStringLiteral("c1") << QStringLiteral("c1") << QStringLiteral("c3") << QStringLiteral("reload")
0055                                                   << QStringLiteral("r1") << QStringLiteral("r1") << QStringLiteral("r3") << QStringLiteral("rn"));
0056         QTest::newRow("more") << (QStringList() << QStringLiteral("c1") << QStringLiteral("c2") << QStringLiteral("c3") << QStringLiteral("reload")
0057                                                 << QStringLiteral("r1") << QStringLiteral("reload") << QStringLiteral("c4") << QStringLiteral("reload")
0058                                                 << QStringLiteral("r2") << QStringLiteral("reload") << QStringLiteral("r3") << QStringLiteral("r4")
0059                                                 << QStringLiteral("rn"));
0060         // FIXME: Due to the event compression in the server we simply expect a removal signal
0061         // QTest::newRow("modifyThenDelete") << (QStringList() << "c1" << "d1" << "r1" << "rn");
0062     }
0063 
0064     void testChangeRecorder()
0065     {
0066         QFETCH(QStringList, actions);
0067         QString lastAction;
0068 
0069         auto rec = createChangeRecorder();
0070         QVERIFY(rec->isEmpty());
0071         for (const QString &action : std::as_const(actions)) {
0072             qDebug() << action;
0073             if (action == QLatin1StringView("rn")) {
0074                 replayNextAndExpectNothing(rec.get());
0075             } else if (action == QLatin1StringView("reload")) {
0076                 // Check saving and loading from disk
0077                 rec = createChangeRecorder();
0078             } else if (action.at(0) == QLatin1Char('c')) {
0079                 // c1 = "trigger change on item 1"
0080                 const int id = QStringView(action).mid(1).toInt();
0081                 Q_ASSERT(id);
0082                 triggerChange(id);
0083                 if (action != lastAction) {
0084                     // enter event loop and wait for change notifications from the server
0085                     QVERIFY(AkonadiTest::akWaitForSignal(rec.get(), &ChangeRecorder::changesAdded, 1000));
0086                 }
0087             } else if (action.at(0) == QLatin1Char('d')) {
0088                 // d1 = "delete item 1"
0089                 const int id = QStringView(action).mid(1).toInt();
0090                 Q_ASSERT(id);
0091                 triggerDelete(id);
0092                 QTest::qWait(500);
0093             } else if (action.at(0) == QLatin1Char('r')) {
0094                 // r1 = "replayNext and expect to get itemChanged(1)"
0095                 const int id = QStringView(action).mid(1).toInt();
0096                 Q_ASSERT(id);
0097                 replayNextAndProcess(rec.get(), id);
0098             } else {
0099                 QVERIFY2(false, qPrintable(QStringLiteral("Unsupported: ") + action));
0100             }
0101             lastAction = action;
0102         }
0103         QVERIFY(rec->isEmpty());
0104     }
0105 
0106 private:
0107     void triggerChange(Akonadi::Item::Id uid)
0108     {
0109         static int s_num = 0;
0110         Item item(uid);
0111         auto attr = item.attribute<TestAttribute>(Item::AddIfMissing);
0112         attr->data = QByteArray::number(++s_num);
0113         auto job = new ItemModifyJob(item);
0114         job->disableRevisionCheck();
0115         AKVERIFYEXEC(job);
0116     }
0117 
0118     void triggerDelete(Akonadi::Item::Id uid)
0119     {
0120         Item item(uid);
0121         auto job = new ItemDeleteJob(item);
0122         AKVERIFYEXEC(job);
0123     }
0124 
0125     void replayNextAndProcess(ChangeRecorder *rec, Akonadi::Item::Id expectedUid)
0126     {
0127         QSignalSpy nothingSpy(rec, &ChangeRecorder::nothingToReplay);
0128         QVERIFY(nothingSpy.isValid());
0129         QSignalSpy itemChangedSpy(rec, &Monitor::itemChanged);
0130         QVERIFY(itemChangedSpy.isValid());
0131 
0132         rec->replayNext();
0133         if (itemChangedSpy.isEmpty()) {
0134             QVERIFY(AkonadiTest::akWaitForSignal(rec, &Monitor::itemChanged, 1000));
0135         }
0136         QCOMPARE(itemChangedSpy.count(), 1);
0137         QCOMPARE(itemChangedSpy.at(0).at(0).value<Akonadi::Item>().id(), expectedUid);
0138 
0139         rec->changeProcessed();
0140 
0141         QCOMPARE(nothingSpy.count(), 0);
0142     }
0143 
0144     void replayNextAndExpectNothing(ChangeRecorder *rec)
0145     {
0146         QSignalSpy nothingSpy(rec, &ChangeRecorder::nothingToReplay);
0147         QVERIFY(nothingSpy.isValid());
0148         QSignalSpy itemChangedSpy(rec, &Monitor::itemChanged);
0149         QVERIFY(itemChangedSpy.isValid());
0150 
0151         rec->replayNext(); // emits nothingToReplay immediately
0152 
0153         QCOMPARE(itemChangedSpy.count(), 0);
0154         QCOMPARE(nothingSpy.count(), 1);
0155     }
0156 
0157     std::unique_ptr<ChangeRecorder> createChangeRecorder() const
0158     {
0159         auto rec = std::make_unique<ChangeRecorder>();
0160         rec->setConfig(settings);
0161         rec->setAllMonitored();
0162         rec->itemFetchScope().fetchFullPayload();
0163         rec->itemFetchScope().fetchAllAttributes();
0164         rec->itemFetchScope().setCacheOnly(true);
0165 
0166         // Ensure we listen to a signal, otherwise MonitorPrivate::isLazilyIgnored will ignore notifications
0167         auto spy = new QSignalSpy(rec.get(), &Monitor::itemChanged);
0168         spy->setParent(rec.get());
0169 
0170         QSignalSpy readySpy(rec.get(), &Monitor::monitorReady);
0171         if (!readySpy.wait()) {
0172             QTest::qFail("Failed to wait for Monitor", __FILE__, __LINE__);
0173             return nullptr;
0174         }
0175 
0176         return rec;
0177     }
0178 
0179     QSettings *settings = nullptr;
0180 };
0181 
0182 QTEST_AKONADIMAIN(ChangeRecorderTest)
0183 
0184 #include "changerecordertest.moc"