diff --git a/src/libsync/lockfilejobs.cpp b/src/libsync/lockfilejobs.cpp index 2d3dfcb52..4a398c2b0 100644 --- a/src/libsync/lockfilejobs.cpp +++ b/src/libsync/lockfilejobs.cpp @@ -65,14 +65,14 @@ bool LockFileJob::finished() const auto httpErrorCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (httpErrorCode == LOCKED_HTTP_ERROR_CODE) { const auto record = handleReply(); - if (static_cast(record._lockOwnerType) == SyncFileItem::LockOwnerType::UserLock) { - Q_EMIT finishedWithError(httpErrorCode, {}, record._lockOwnerDisplayName); + if (static_cast(record._lockstate._lockOwnerType) == SyncFileItem::LockOwnerType::UserLock) { + Q_EMIT finishedWithError(httpErrorCode, {}, record._lockstate._lockOwnerDisplayName); } else { - Q_EMIT finishedWithError(httpErrorCode, {}, record._lockEditorApp); + Q_EMIT finishedWithError(httpErrorCode, {}, record._lockstate._lockEditorApp); } } else if (httpErrorCode == PRECONDITION_FAILED_ERROR_CODE) { const auto record = handleReply(); - if (_requestedLockState == SyncFileItem::LockStatus::UnlockedItem && !record._locked) { + if (_requestedLockState == SyncFileItem::LockStatus::UnlockedItem && !record._lockstate._locked) { Q_EMIT finishedWithoutError(); } else { Q_EMIT finishedWithError(httpErrorCode, reply()->errorString(), {}); @@ -90,13 +90,13 @@ bool LockFileJob::finished() void LockFileJob::setFileRecordLocked(SyncJournalFileRecord &record) const { - record._locked = (_lockStatus == SyncFileItem::LockStatus::LockedItem); - record._lockOwnerType = static_cast(_lockOwnerType); - record._lockOwnerDisplayName = _userDisplayName; - record._lockOwnerId = _userId; - record._lockEditorApp = _editorName; - record._lockTime = _lockTime; - record._lockTimeout = _lockTimeout; + record._lockstate._locked = (_lockStatus == SyncFileItem::LockStatus::LockedItem); + record._lockstate._lockOwnerType = static_cast(_lockOwnerType); + record._lockstate._lockOwnerDisplayName = _userDisplayName; + record._lockstate._lockOwnerId = _userId; + record._lockstate._lockEditorApp = _editorName; + record._lockstate._lockTime = _lockTime; + record._lockstate._lockTimeout = _lockTimeout; } void LockFileJob::resetState() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7c142f4a6..b119c384d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -64,6 +64,7 @@ nextcloud_add_test(UnifiedSearchListmodel) nextcloud_add_test(ActivityListModel) nextcloud_add_test(ActivityData) nextcloud_add_test(TalkReply) +nextcloud_add_test(LockFile) if( UNIX AND NOT APPLE ) nextcloud_add_test(InotifyWatcher) diff --git a/test/syncenginetestutils.cpp b/test/syncenginetestutils.cpp index 86873ca8f..340cf82ba 100644 --- a/test/syncenginetestutils.cpp +++ b/test/syncenginetestutils.cpp @@ -1000,6 +1000,8 @@ QNetworkReply *FakeQNAM::createRequest(QNetworkAccessManager::Operation op, cons if (contentType.startsWith(QStringLiteral("multipart/related; boundary="))) { reply = new FakePutMultiFileReply { info, op, newRequest, contentType, outgoingData->readAll(), this }; } + } else if (verb == QLatin1String("LOCK") || verb == QLatin1String("UNLOCK")) { + reply = new FakeFileLockReply{info, op, newRequest, this}; } else { qDebug() << verb << outgoingData; Q_UNREACHABLE(); @@ -1251,3 +1253,50 @@ FakeJsonErrorReply::FakeJsonErrorReply(QNetworkAccessManager::Operation op, : FakeErrorReply{ op, request, parent, httpErrorCode, reply.toJson() } { } + +FakeFileLockReply::FakeFileLockReply(FileInfo &remoteRootFileInfo, + QNetworkAccessManager::Operation op, + const QNetworkRequest &request, + QObject *parent) + : FakePropfindReply(remoteRootFileInfo, op, request, parent) +{ + const auto verb = request.attribute(QNetworkRequest::CustomVerbAttribute); + + setRequest(request); + setUrl(request.url()); + setOperation(op); + open(QIODevice::ReadOnly); + + QString fileName = getFilePathFromUrl(request.url()); + Q_ASSERT(!fileName.isNull()); // for root, it should be empty + FileInfo *fileInfo = remoteRootFileInfo.find(fileName); + if (!fileInfo) { + QMetaObject::invokeMethod(this, "respond404", Qt::QueuedConnection); + return; + } + + const QString prefix = request.url().path().left(request.url().path().size() - fileName.size()); + + // Don't care about the request and just return a full propfind + const QString davUri { QStringLiteral("DAV:") }; + const QString ocUri { QStringLiteral("http://owncloud.org/ns") }; + const QString ncUri { QStringLiteral("http://nextcloud.org/ns") }; + payload.clear(); + QBuffer buffer { &payload }; + buffer.open(QIODevice::WriteOnly); + QXmlStreamWriter xml(&buffer); + xml.writeNamespace(davUri, QStringLiteral("d")); + xml.writeNamespace(ocUri, QStringLiteral("oc")); + xml.writeNamespace(ncUri, QStringLiteral("nc")); + xml.writeStartDocument(); + xml.writeStartElement(davUri, QStringLiteral("prop")); + xml.writeTextElement(ncUri, QStringLiteral("lock"), verb == QStringLiteral("LOCK") ? "1" : "0"); + xml.writeTextElement(ncUri, QStringLiteral("lock-owner-type"), QString::number(0)); + xml.writeTextElement(ncUri, QStringLiteral("lock-owner"), QStringLiteral("admin")); + xml.writeTextElement(ncUri, QStringLiteral("lock-owner-displayname"), QStringLiteral("John Doe")); + xml.writeTextElement(ncUri, QStringLiteral("lock-owner-editor"), {}); + xml.writeTextElement(ncUri, QStringLiteral("lock-time"), QString::number(1234560)); + xml.writeTextElement(ncUri, QStringLiteral("lock-timeout"), QString::number(1800)); + xml.writeEndElement(); // prop + xml.writeEndDocument(); +} diff --git a/test/syncenginetestutils.h b/test/syncenginetestutils.h index 8abe50848..27311f2b0 100644 --- a/test/syncenginetestutils.h +++ b/test/syncenginetestutils.h @@ -402,6 +402,16 @@ public: qint64 readData(char *, qint64) override { return 0; } }; +class FakeFileLockReply : public FakePropfindReply +{ + Q_OBJECT +public: + FakeFileLockReply(FileInfo &remoteRootFileInfo, + QNetworkAccessManager::Operation op, + const QNetworkRequest &request, + QObject *parent); +}; + // A delayed reply template class DelayedReply : public OriginalReply diff --git a/test/testlocaldiscovery.cpp b/test/testlocaldiscovery.cpp index c3719d0fc..29c80e9a4 100644 --- a/test/testlocaldiscovery.cpp +++ b/test/testlocaldiscovery.cpp @@ -597,8 +597,6 @@ private slots: void testDiscoverLockChanges() { -// Logger::instance()->setLogDebug(true); - FakeFolder fakeFolder{FileInfo{}}; fakeFolder.syncEngine().account()->setCapabilities({{"activity", QVariantMap{{"apiv2", QVariantList{"filters", "filters-api", "previews", "rich-strings"}}}}, {"bruteforce", QVariantMap{{"delay", 0}}}, @@ -626,7 +624,7 @@ private slots: "user1" "user1" "user1" - "164804670720020"; + "1648046707"; fakeFolder.remoteModifier().mkdir(QStringLiteral("subfolder")); fakeFolder.remoteModifier().insert(fooFileSubFolder); diff --git a/test/testlockfile.cpp b/test/testlockfile.cpp new file mode 100644 index 000000000..78e76e6a8 --- /dev/null +++ b/test/testlockfile.cpp @@ -0,0 +1,381 @@ +#include "lockfilejobs.h" + +#include "account.h" +#include "accountstate.h" +#include "common/syncjournaldb.h" +#include "common/syncjournalfilerecord.h" +#include "syncenginetestutils.h" + +#include +#include + +class TestLockFile : public QObject +{ + Q_OBJECT + +public: + TestLockFile() = default; + +private slots: + void initTestCase() + { + } + + void testLockFile_lockFile_jobSuccess() + { + const auto testFileName = QStringLiteral("file.txt"); + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.localModifier().insert(testFileName); + + QVERIFY(fakeFolder.syncOnce()); + + auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem); + + QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); + + job->start(); + + QVERIFY(jobSuccess.wait()); + QCOMPARE(jobFailure.count(), 0); + + auto fileRecord = OCC::SyncJournalFileRecord{}; + QVERIFY(fakeFolder.syncJournal().getFileRecord(testFileName, &fileRecord)); + QCOMPARE(fileRecord._lockstate._locked, true); + QCOMPARE(fileRecord._lockstate._lockEditorApp, QString{}); + QCOMPARE(fileRecord._lockstate._lockOwnerDisplayName, QStringLiteral("John Doe")); + QCOMPARE(fileRecord._lockstate._lockOwnerId, QStringLiteral("admin")); + QCOMPARE(fileRecord._lockstate._lockOwnerType, static_cast(OCC::SyncFileItem::LockOwnerType::UserLock)); + QCOMPARE(fileRecord._lockstate._lockTime, 1234560); + QCOMPARE(fileRecord._lockstate._lockTimeout, 1800); + + QVERIFY(fakeFolder.syncOnce()); + } + + void testLockFile_lockFile_unlockFile_jobSuccess() + { + const auto testFileName = QStringLiteral("file.txt"); + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.localModifier().insert(testFileName); + + QVERIFY(fakeFolder.syncOnce()); + + auto lockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem); + + QSignalSpy lockFileJobSuccess(lockFileJob, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy lockFileJobFailure(lockFileJob, &OCC::LockFileJob::finishedWithError); + + lockFileJob->start(); + + QVERIFY(lockFileJobSuccess.wait()); + QCOMPARE(lockFileJobFailure.count(), 0); + + QVERIFY(fakeFolder.syncOnce()); + + auto unlockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem); + + QSignalSpy unlockFileJobSuccess(unlockFileJob, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy unlockFileJobFailure(unlockFileJob, &OCC::LockFileJob::finishedWithError); + + unlockFileJob->start(); + + QVERIFY(unlockFileJobSuccess.wait()); + QCOMPARE(unlockFileJobFailure.count(), 0); + + auto fileRecord = OCC::SyncJournalFileRecord{}; + QVERIFY(fakeFolder.syncJournal().getFileRecord(testFileName, &fileRecord)); + QCOMPARE(fileRecord._lockstate._locked, false); + + QVERIFY(fakeFolder.syncOnce()); + } + + void testLockFile_lockFile_alreadyLockedByUser() + { + static constexpr auto LockedHttpErrorCode = 423; + static constexpr auto PreconditionFailedHttpErrorCode = 412; + + const auto testFileName = QStringLiteral("file.txt"); + + const auto replyData = QByteArray("\n" + "\n" + " 1\n" + " 0\n" + " john\n" + " John Doe\n" + " john\n" + " 1650619678\n" + " 300\n" + " files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202\n" + "\n"); + + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) { + QNetworkReply *reply = nullptr; + if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) { + reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData); + } else if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK")) { + reply = new FakeErrorReply(op, request, nullptr, PreconditionFailedHttpErrorCode, replyData); + } + + return reply; + }); + + fakeFolder.localModifier().insert(testFileName); + + QVERIFY(fakeFolder.syncOnce()); + + auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem); + + QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); + + job->start(); + + QVERIFY(jobFailure.wait()); + QCOMPARE(jobSuccess.count(), 0); + } + + void testLockFile_lockFile_alreadyLockedByApp() + { + static constexpr auto LockedHttpErrorCode = 423; + static constexpr auto PreconditionFailedHttpErrorCode = 412; + + const auto testFileName = QStringLiteral("file.txt"); + + const auto replyData = QByteArray("\n" + "\n" + " 1\n" + " 1\n" + " john\n" + " John Doe\n" + " Text\n" + " 1650619678\n" + " 300\n" + " files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202\n" + "\n"); + + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) { + QNetworkReply *reply = nullptr; + if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) { + reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData); + } else if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK")) { + reply = new FakeErrorReply(op, request, nullptr, PreconditionFailedHttpErrorCode, replyData); + } + + return reply; + }); + + fakeFolder.localModifier().insert(testFileName); + + QVERIFY(fakeFolder.syncOnce()); + + auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem); + + QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); + + job->start(); + + QVERIFY(jobFailure.wait()); + QCOMPARE(jobSuccess.count(), 0); + } + + void testLockFile_unlockFile_alreadyUnlocked() + { + static constexpr auto LockedHttpErrorCode = 423; + static constexpr auto PreconditionFailedHttpErrorCode = 412; + + const auto testFileName = QStringLiteral("file.txt"); + + const auto replyData = QByteArray("\n" + "\n" + " \n" + " 0\n" + " john\n" + " John Doe\n" + " john\n" + " 1650619678\n" + " 300\n" + " files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202\n" + "\n"); + + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) { + QNetworkReply *reply = nullptr; + if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) { + reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData); + } else if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK")) { + reply = new FakeErrorReply(op, request, nullptr, PreconditionFailedHttpErrorCode, replyData); + } + + return reply; + }); + + fakeFolder.localModifier().insert(testFileName); + + QVERIFY(fakeFolder.syncOnce()); + + auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem); + + QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); + + job->start(); + + QVERIFY(jobSuccess.wait()); + QCOMPARE(jobFailure.count(), 0); + } + + void testLockFile_unlockFile_lockedBySomeoneElse() + { + static constexpr auto LockedHttpErrorCode = 423; + + const auto testFileName = QStringLiteral("file.txt"); + + const auto replyData = QByteArray("\n" + "\n" + " 1\n" + " 0\n" + " alice\n" + " Alice Doe\n" + " Text\n" + " 1650619678\n" + " 300\n" + " files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202\n" + "\n"); + + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) { + QNetworkReply *reply = nullptr; + if (op == QNetworkAccessManager::CustomOperation && (request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK") || + request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK"))) { + reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData); + } + + return reply; + }); + + fakeFolder.localModifier().insert(testFileName); + + QVERIFY(fakeFolder.syncOnce()); + + auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem); + + QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); + + job->start(); + + QVERIFY(jobFailure.wait()); + QCOMPARE(jobSuccess.count(), 0); + } + + void testLockFile_lockFile_jobError() + { + const auto testFileName = QStringLiteral("file.txt"); + static constexpr auto InternalServerErrorHttpErrorCode = 500; + + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.setServerOverride([] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) { + QNetworkReply *reply = nullptr; + if (op == QNetworkAccessManager::CustomOperation && (request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK") || + request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK"))) { + reply = new FakeErrorReply(op, request, nullptr, InternalServerErrorHttpErrorCode, {}); + } + + return reply; + }); + + fakeFolder.localModifier().insert(QStringLiteral("file.txt")); + + QVERIFY(fakeFolder.syncOnce()); + + auto lockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem); + + QSignalSpy lockFileJobSuccess(lockFileJob, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy lockFileJobFailure(lockFileJob, &OCC::LockFileJob::finishedWithError); + + lockFileJob->start(); + + QVERIFY(lockFileJobFailure.wait()); + QCOMPARE(lockFileJobSuccess.count(), 0); + + QVERIFY(fakeFolder.syncOnce()); + + auto unlockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem); + + QSignalSpy unlockFileJobSuccess(unlockFileJob, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy unlockFileJobFailure(unlockFileJob, &OCC::LockFileJob::finishedWithError); + + unlockFileJob->start(); + + QVERIFY(unlockFileJobFailure.wait()); + QCOMPARE(unlockFileJobSuccess.count(), 0); + + QVERIFY(fakeFolder.syncOnce()); + } + + void testLockFile_lockFile_preconditionFailedError() + { + static constexpr auto PreconditionFailedHttpErrorCode = 412; + + const auto testFileName = QStringLiteral("file.txt"); + + const auto replyData = QByteArray("\n" + "\n" + " 1\n" + " 0\n" + " alice\n" + " Alice Doe\n" + " Text\n" + " 1650619678\n" + " 300\n" + " files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202\n" + "\n"); + + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) { + QNetworkReply *reply = nullptr; + if (op == QNetworkAccessManager::CustomOperation && (request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK") || + request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK"))) { + reply = new FakeErrorReply(op, request, nullptr, PreconditionFailedHttpErrorCode, replyData); + } + + return reply; + }); + + fakeFolder.localModifier().insert(testFileName); + + QVERIFY(fakeFolder.syncOnce()); + + auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem); + + QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); + + job->start(); + + QVERIFY(jobFailure.wait()); + QCOMPARE(jobSuccess.count(), 0); + } +}; + +QTEST_GUILESS_MAIN(TestLockFile) +#include "testlockfile.moc" diff --git a/test/testlockfilejobs.cpp b/test/testlockfilejobs.cpp new file mode 100644 index 000000000..6e0b5f7dc --- /dev/null +++ b/test/testlockfilejobs.cpp @@ -0,0 +1,242 @@ +#include "lockfilejobs.h" + +#include "account.h" +#include "accountstate.h" +#include "common/syncjournaldb.h" +#include "common/syncjournalfilerecord.h" +#include "syncenginetestutils.h" + +#include +#include + +class TestLockFileJobs : public QObject +{ + Q_OBJECT + +public: + TestLockFileJobs() = default; + +private slots: + void initTestCase() + { + } + + void testLockFileJob_lockFile_jobSuccess() + { + const auto testFileName = QStringLiteral("file.txt"); + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.localModifier().insert(testFileName); + + QVERIFY(fakeFolder.syncOnce()); + + auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem); + + QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); + + job->start(); + + QVERIFY(jobSuccess.wait()); + QCOMPARE(jobFailure.count(), 0); + + auto fileRecord = OCC::SyncJournalFileRecord{}; + QVERIFY(fakeFolder.syncJournal().getFileRecord(testFileName, &fileRecord)); + QCOMPARE(fileRecord._locked, true); + QCOMPARE(fileRecord._lockEditorApp, QString{}); + QCOMPARE(fileRecord._lockOwnerDisplayName, QStringLiteral("John Doe")); + QCOMPARE(fileRecord._lockOwnerId, QStringLiteral("john")); + QCOMPARE(fileRecord._lockOwnerType, static_cast(OCC::SyncFileItem::LockOwnerType::UserLock)); + QCOMPARE(fileRecord._lockTime, 1234560); + QCOMPARE(fileRecord._lockTimeout, 1800); + + QVERIFY(fakeFolder.syncOnce()); + } + + void testLockFileJob_lockFile_unlockFile_jobSuccess() + { + const auto testFileName = QStringLiteral("file.txt"); + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.localModifier().insert(testFileName); + + QVERIFY(fakeFolder.syncOnce()); + + auto lockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem); + + QSignalSpy lockFileJobSuccess(lockFileJob, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy lockFileJobFailure(lockFileJob, &OCC::LockFileJob::finishedWithError); + + lockFileJob->start(); + + QVERIFY(lockFileJobSuccess.wait()); + QCOMPARE(lockFileJobFailure.count(), 0); + + QVERIFY(fakeFolder.syncOnce()); + + auto unlockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem); + + QSignalSpy unlockFileJobSuccess(unlockFileJob, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy unlockFileJobFailure(unlockFileJob, &OCC::LockFileJob::finishedWithError); + + unlockFileJob->start(); + + QVERIFY(unlockFileJobSuccess.wait()); + QCOMPARE(unlockFileJobFailure.count(), 0); + + auto fileRecord = OCC::SyncJournalFileRecord{}; + QVERIFY(fakeFolder.syncJournal().getFileRecord(testFileName, &fileRecord)); + QCOMPARE(fileRecord._locked, false); + + QVERIFY(fakeFolder.syncOnce()); + } + + void testLockFileJob_lockFile_alreadyLocked() + { + static constexpr auto LockedHttpErrorCode = 423; + static constexpr auto PreconditionFailedHttpErrorCode = 412; + + const auto testFileName = QStringLiteral("file.txt"); + + const auto replyData = QByteArray("\n" + "\n" + " 1\n" + " 0\n" + " john\n" + " John Doe\n" + " john\n" + " 1650619678\n" + " 300\n" + " files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202\n" + "\n"); + + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) { + QNetworkReply *reply = nullptr; + if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) { + reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData); + } else if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK")) { + reply = new FakeErrorReply(op, request, nullptr, PreconditionFailedHttpErrorCode, replyData); + } + + return reply; + }); + + fakeFolder.localModifier().insert(testFileName); + + QVERIFY(fakeFolder.syncOnce()); + + auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem); + + QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); + + job->start(); + + QVERIFY(jobFailure.wait()); + QCOMPARE(jobSuccess.count(), 0); + } + + void testLockFileJob_unlockFile_alreadyUnlocked() + { + static constexpr auto LockedHttpErrorCode = 423; + static constexpr auto PreconditionFailedHttpErrorCode = 412; + + const auto testFileName = QStringLiteral("file.txt"); + + const auto replyData = QByteArray("\n" + "\n" + " \n" + " 0\n" + " john\n" + " John Doe\n" + " john\n" + " 1650619678\n" + " 300\n" + " files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202\n" + "\n"); + + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) { + QNetworkReply *reply = nullptr; + if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) { + reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData); + } else if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK")) { + reply = new FakeErrorReply(op, request, nullptr, PreconditionFailedHttpErrorCode, replyData); + } + + return reply; + }); + + fakeFolder.localModifier().insert(testFileName); + + QVERIFY(fakeFolder.syncOnce()); + + auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem); + + QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError); + + job->start(); + + QVERIFY(jobFailure.wait()); + QCOMPARE(jobSuccess.count(), 0); + } + + void testLockFileJob_lockFile_jobError() + { + const auto testFileName = QStringLiteral("file.txt"); + static constexpr auto InternalServerErrorHttpErrorCode = 500; + + FakeFolder fakeFolder{FileInfo{}}; + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + fakeFolder.setServerOverride([] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) { + QNetworkReply *reply = nullptr; + if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) { + reply = new FakeErrorReply(op, request, nullptr, InternalServerErrorHttpErrorCode, {}); + } else if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK")) { + reply = new FakeErrorReply(op, request, nullptr, InternalServerErrorHttpErrorCode, {}); + } + + return reply; + }); + + fakeFolder.localModifier().insert(QStringLiteral("file.txt")); + + QVERIFY(fakeFolder.syncOnce()); + + auto lockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem); + + QSignalSpy lockFileJobSuccess(lockFileJob, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy lockFileJobFailure(lockFileJob, &OCC::LockFileJob::finishedWithError); + + lockFileJob->start(); + + QVERIFY(lockFileJobFailure.wait()); + QCOMPARE(lockFileJobSuccess.count(), 0); + + QVERIFY(fakeFolder.syncOnce()); + + auto unlockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem); + + QSignalSpy unlockFileJobSuccess(unlockFileJob, &OCC::LockFileJob::finishedWithoutError); + QSignalSpy unlockFileJobFailure(unlockFileJob, &OCC::LockFileJob::finishedWithError); + + unlockFileJob->start(); + + QVERIFY(unlockFileJobFailure.wait()); + QCOMPARE(unlockFileJobSuccess.count(), 0); + + QVERIFY(fakeFolder.syncOnce()); + } +}; + +QTEST_MAIN(TestLockFileJobs) +#include "testlockfilejobs.moc"