const auto httpErrorCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (httpErrorCode == LOCKED_HTTP_ERROR_CODE) {
const auto record = handleReply();
- if (static_cast<SyncFileItem::LockOwnerType>(record._lockOwnerType) == SyncFileItem::LockOwnerType::UserLock) {
- Q_EMIT finishedWithError(httpErrorCode, {}, record._lockOwnerDisplayName);
+ if (static_cast<SyncFileItem::LockOwnerType>(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(), {});
void LockFileJob::setFileRecordLocked(SyncJournalFileRecord &record) const
{
- record._locked = (_lockStatus == SyncFileItem::LockStatus::LockedItem);
- record._lockOwnerType = static_cast<int>(_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<int>(_lockOwnerType);
+ record._lockstate._lockOwnerDisplayName = _userDisplayName;
+ record._lockstate._lockOwnerId = _userId;
+ record._lockstate._lockEditorApp = _editorName;
+ record._lockstate._lockTime = _lockTime;
+ record._lockstate._lockTimeout = _lockTimeout;
}
void LockFileJob::resetState()
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)
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();
: 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();
+}
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 OriginalReply>
class DelayedReply : public OriginalReply
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}}},
"<nc:lock-owner>user1</nc:lock-owner>"
"<nc:lock-owner-displayname>user1</nc:lock-owner-displayname>"
"<nc:lock-owner-editor>user1</nc:lock-owner-editor>"
- "<nc:lock-time>1648046707</nc:lock-time><oc:size>20020</oc:size>";
+ "<nc:lock-time>1648046707</nc:lock-time>";
fakeFolder.remoteModifier().mkdir(QStringLiteral("subfolder"));
fakeFolder.remoteModifier().insert(fooFileSubFolder);
--- /dev/null
+#include "lockfilejobs.h"
+
+#include "account.h"
+#include "accountstate.h"
+#include "common/syncjournaldb.h"
+#include "common/syncjournalfilerecord.h"
+#include "syncenginetestutils.h"
+
+#include <QTest>
+#include <QSignalSpy>
+
+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<qint64>(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("<?xml version=\"1.0\"?>\n"
+ "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
+ " <nc:lock>1</nc:lock>\n"
+ " <nc:lock-owner-type>0</nc:lock-owner-type>\n"
+ " <nc:lock-owner>john</nc:lock-owner>\n"
+ " <nc:lock-owner-displayname>John Doe</nc:lock-owner-displayname>\n"
+ " <nc:lock-owner-editor>john</nc:lock-owner-editor>\n"
+ " <nc:lock-time>1650619678</nc:lock-time>\n"
+ " <nc:lock-timeout>300</nc:lock-timeout>\n"
+ " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
+ "</d:prop>\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("<?xml version=\"1.0\"?>\n"
+ "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
+ " <nc:lock>1</nc:lock>\n"
+ " <nc:lock-owner-type>1</nc:lock-owner-type>\n"
+ " <nc:lock-owner>john</nc:lock-owner>\n"
+ " <nc:lock-owner-displayname>John Doe</nc:lock-owner-displayname>\n"
+ " <nc:lock-owner-editor>Text</nc:lock-owner-editor>\n"
+ " <nc:lock-time>1650619678</nc:lock-time>\n"
+ " <nc:lock-timeout>300</nc:lock-timeout>\n"
+ " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
+ "</d:prop>\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("<?xml version=\"1.0\"?>\n"
+ "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
+ " <nc:lock/>\n"
+ " <nc:lock-owner-type>0</nc:lock-owner-type>\n"
+ " <nc:lock-owner>john</nc:lock-owner>\n"
+ " <nc:lock-owner-displayname>John Doe</nc:lock-owner-displayname>\n"
+ " <nc:lock-owner-editor>john</nc:lock-owner-editor>\n"
+ " <nc:lock-time>1650619678</nc:lock-time>\n"
+ " <nc:lock-timeout>300</nc:lock-timeout>\n"
+ " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
+ "</d:prop>\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("<?xml version=\"1.0\"?>\n"
+ "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
+ " <nc:lock>1</nc:lock>\n"
+ " <nc:lock-owner-type>0</nc:lock-owner-type>\n"
+ " <nc:lock-owner>alice</nc:lock-owner>\n"
+ " <nc:lock-owner-displayname>Alice Doe</nc:lock-owner-displayname>\n"
+ " <nc:lock-owner-editor>Text</nc:lock-owner-editor>\n"
+ " <nc:lock-time>1650619678</nc:lock-time>\n"
+ " <nc:lock-timeout>300</nc:lock-timeout>\n"
+ " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
+ "</d:prop>\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("<?xml version=\"1.0\"?>\n"
+ "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
+ " <nc:lock>1</nc:lock>\n"
+ " <nc:lock-owner-type>0</nc:lock-owner-type>\n"
+ " <nc:lock-owner>alice</nc:lock-owner>\n"
+ " <nc:lock-owner-displayname>Alice Doe</nc:lock-owner-displayname>\n"
+ " <nc:lock-owner-editor>Text</nc:lock-owner-editor>\n"
+ " <nc:lock-time>1650619678</nc:lock-time>\n"
+ " <nc:lock-timeout>300</nc:lock-timeout>\n"
+ " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
+ "</d:prop>\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"
--- /dev/null
+#include "lockfilejobs.h"
+
+#include "account.h"
+#include "accountstate.h"
+#include "common/syncjournaldb.h"
+#include "common/syncjournalfilerecord.h"
+#include "syncenginetestutils.h"
+
+#include <QTest>
+#include <QSignalSpy>
+
+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<qint64>(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("<?xml version=\"1.0\"?>\n"
+ "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
+ " <nc:lock>1</nc:lock>\n"
+ " <nc:lock-owner-type>0</nc:lock-owner-type>\n"
+ " <nc:lock-owner>john</nc:lock-owner>\n"
+ " <nc:lock-owner-displayname>John Doe</nc:lock-owner-displayname>\n"
+ " <nc:lock-owner-editor>john</nc:lock-owner-editor>\n"
+ " <nc:lock-time>1650619678</nc:lock-time>\n"
+ " <nc:lock-timeout>300</nc:lock-timeout>\n"
+ " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
+ "</d:prop>\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("<?xml version=\"1.0\"?>\n"
+ "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
+ " <nc:lock/>\n"
+ " <nc:lock-owner-type>0</nc:lock-owner-type>\n"
+ " <nc:lock-owner>john</nc:lock-owner>\n"
+ " <nc:lock-owner-displayname>John Doe</nc:lock-owner-displayname>\n"
+ " <nc:lock-owner-editor>john</nc:lock-owner-editor>\n"
+ " <nc:lock-time>1650619678</nc:lock-time>\n"
+ " <nc:lock-timeout>300</nc:lock-timeout>\n"
+ " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
+ "</d:prop>\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"