mirror of
https://github.com/nextcloud/desktop.git
synced 2025-10-26 11:17:43 +00:00
the file being locked may have been changed sinc the client synced it so the server side file may have a modified etag. In such cases we do not want to lock the file and would rather sync the nex server changes before being able to lock the file that would ensure that on client side the file being locked is matching teh state of teh file on server side and would prevent inadvertently overriding server changes Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
796 lines
38 KiB
C++
796 lines
38 KiB
C++
#include "lockfilejobs.h"
|
|
|
|
#include "account.h"
|
|
#include "accountstate.h"
|
|
#include "common/syncjournaldb.h"
|
|
#include "common/syncjournalfilerecord.h"
|
|
#include "syncenginetestutils.h"
|
|
#include "localdiscoverytracker.h"
|
|
|
|
#include <QTest>
|
|
#include <QSignalSpy>
|
|
|
|
class TestLockFile : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
TestLockFile() = default;
|
|
|
|
private slots:
|
|
void initTestCase()
|
|
{
|
|
OCC::Logger::instance()->setLogFlush(true);
|
|
OCC::Logger::instance()->setLogDebug(true);
|
|
|
|
QStandardPaths::setTestModeEnabled(true);
|
|
}
|
|
|
|
void testLockFile_lockFile_lockSuccess()
|
|
{
|
|
const auto testFileName = QStringLiteral("file.txt");
|
|
|
|
FakeFolder fakeFolder{FileInfo{}};
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
QSignalSpy lockFileSuccessSpy(fakeFolder.account().data(), &OCC::Account::lockFileSuccess);
|
|
QSignalSpy lockFileErrorSpy(fakeFolder.account().data(), &OCC::Account::lockFileError);
|
|
|
|
fakeFolder.localModifier().insert(testFileName);
|
|
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
&fakeFolder.syncJournal(),
|
|
OCC::SyncFileItem::LockStatus::LockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
QVERIFY(lockFileSuccessSpy.wait());
|
|
QCOMPARE(lockFileErrorSpy.count(), 0);
|
|
}
|
|
|
|
void testLockFile_lockFile_lockError()
|
|
{
|
|
const auto testFileName = QStringLiteral("file.txt");
|
|
static constexpr auto LockedHttpErrorCode = 423;
|
|
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);
|
|
}
|
|
|
|
return reply;
|
|
});
|
|
|
|
QSignalSpy lockFileSuccessSpy(fakeFolder.account().data(), &OCC::Account::lockFileSuccess);
|
|
QSignalSpy lockFileErrorSpy(fakeFolder.account().data(), &OCC::Account::lockFileError);
|
|
|
|
fakeFolder.localModifier().insert(testFileName);
|
|
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
&fakeFolder.syncJournal(),
|
|
OCC::SyncFileItem::LockStatus::LockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
QVERIFY(lockFileErrorSpy.wait());
|
|
QCOMPARE(lockFileSuccessSpy.count(), 0);
|
|
}
|
|
|
|
void testLockFile_fileLockStatus_queryLockStatus()
|
|
{
|
|
const auto testFileName = QStringLiteral("file.txt");
|
|
|
|
FakeFolder fakeFolder{FileInfo{}};
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
QSignalSpy lockFileSuccessSpy(fakeFolder.account().data(), &OCC::Account::lockFileSuccess);
|
|
QSignalSpy lockFileErrorSpy(fakeFolder.account().data(), &OCC::Account::lockFileError);
|
|
|
|
fakeFolder.localModifier().insert(testFileName);
|
|
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
&fakeFolder.syncJournal(),
|
|
OCC::SyncFileItem::LockStatus::LockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
QVERIFY(lockFileSuccessSpy.wait());
|
|
QCOMPARE(lockFileErrorSpy.count(), 0);
|
|
|
|
auto lockStatus = fakeFolder.account()->fileLockStatus(&fakeFolder.syncJournal(), testFileName);
|
|
QCOMPARE(lockStatus, OCC::SyncFileItem::LockStatus::LockedItem);
|
|
}
|
|
|
|
void testLockFile_fileCanBeUnlocked_canUnlock()
|
|
{
|
|
const auto testFileName = QStringLiteral("file.txt");
|
|
|
|
FakeFolder fakeFolder{FileInfo{}};
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
QSignalSpy lockFileSuccessSpy(fakeFolder.account().data(), &OCC::Account::lockFileSuccess);
|
|
QSignalSpy lockFileErrorSpy(fakeFolder.account().data(), &OCC::Account::lockFileError);
|
|
|
|
fakeFolder.localModifier().insert(testFileName);
|
|
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
&fakeFolder.syncJournal(),
|
|
OCC::SyncFileItem::LockStatus::LockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
QVERIFY(lockFileSuccessSpy.wait());
|
|
QCOMPARE(lockFileErrorSpy.count(), 0);
|
|
|
|
auto lockStatus = fakeFolder.account()->fileCanBeUnlocked(&fakeFolder.syncJournal(), testFileName);
|
|
QCOMPARE(lockStatus, true);
|
|
}
|
|
|
|
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,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
OCC::SyncFileItem::LockStatus::LockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
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,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
OCC::SyncFileItem::LockStatus::LockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
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,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
OCC::SyncFileItem::LockStatus::UnlockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
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,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
OCC::SyncFileItem::LockStatus::LockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
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,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
OCC::SyncFileItem::LockStatus::LockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
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,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
OCC::SyncFileItem::LockStatus::UnlockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
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,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
OCC::SyncFileItem::LockStatus::UnlockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
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,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
OCC::SyncFileItem::LockStatus::LockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
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,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
OCC::SyncFileItem::LockStatus::UnlockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
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,
|
|
QStringLiteral("/"),
|
|
fakeFolder.localPath(),
|
|
{},
|
|
OCC::SyncFileItem::LockStatus::UnlockedItem,
|
|
OCC::SyncFileItem::LockOwnerType::UserLock);
|
|
|
|
QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
|
|
QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
|
|
|
|
job->start();
|
|
|
|
QVERIFY(jobFailure.wait());
|
|
QCOMPARE(jobSuccess.count(), 0);
|
|
}
|
|
|
|
void testSyncLockedFilesAlmostExpired()
|
|
{
|
|
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
QSignalSpy spySyncCompleted(&fakeFolder.syncEngine(), &OCC::SyncEngine::finished);
|
|
|
|
ItemCompletedSpy completeSpy(fakeFolder);
|
|
|
|
completeSpy.clear();
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::UnlockedItem);
|
|
OCC::SyncJournalFileRecord fileRecordBefore;
|
|
QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordBefore));
|
|
QVERIFY(!fileRecordBefore._lockstate._locked);
|
|
|
|
fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileLocked, 1, QStringLiteral("Nextcloud Office"), {}, QStringLiteral("richdocuments"), QDateTime::currentDateTime().toSecsSinceEpoch() - 1220, 1226);
|
|
|
|
completeSpy.clear();
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::LockedItem);
|
|
OCC::SyncJournalFileRecord fileRecordLocked;
|
|
QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordLocked));
|
|
QVERIFY(fileRecordLocked._lockstate._locked);
|
|
|
|
spySyncCompleted.clear();
|
|
|
|
QTest::qWait(5000);
|
|
|
|
fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileUnlocked, {}, {}, {}, {}, {}, {});
|
|
|
|
QCOMPARE(spySyncCompleted.count(), 0);
|
|
QVERIFY(spySyncCompleted.wait(3000));
|
|
QCOMPARE(spySyncCompleted.count(), 1);
|
|
|
|
OCC::SyncJournalFileRecord fileRecordUnlocked;
|
|
QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordUnlocked));
|
|
QVERIFY(!fileRecordUnlocked._lockstate._locked);
|
|
}
|
|
|
|
void testSyncLockedFilesNoExpiredLockedFiles()
|
|
{
|
|
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
QSignalSpy spySyncCompleted(&fakeFolder.syncEngine(), &OCC::SyncEngine::finished);
|
|
|
|
ItemCompletedSpy completeSpy(fakeFolder);
|
|
|
|
completeSpy.clear();
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::UnlockedItem);
|
|
OCC::SyncJournalFileRecord fileRecordBefore;
|
|
QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordBefore));
|
|
QVERIFY(!fileRecordBefore._lockstate._locked);
|
|
|
|
fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileLocked, 1, QStringLiteral("Nextcloud Office"), {}, QStringLiteral("richdocuments"), QDateTime::currentDateTime().toSecsSinceEpoch() - 1220, 1226);
|
|
|
|
completeSpy.clear();
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::LockedItem);
|
|
OCC::SyncJournalFileRecord fileRecordLocked;
|
|
QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordLocked));
|
|
QVERIFY(fileRecordLocked._lockstate._locked);
|
|
|
|
completeSpy.clear();
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
spySyncCompleted.clear();
|
|
|
|
QTest::qWait(1000);
|
|
|
|
fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileUnlocked, {}, {}, {}, {}, {}, {});
|
|
|
|
QCOMPARE(spySyncCompleted.count(), 0);
|
|
QVERIFY(!spySyncCompleted.wait(3000));
|
|
QCOMPARE(spySyncCompleted.count(), 0);
|
|
|
|
OCC::SyncJournalFileRecord fileRecordUnlocked;
|
|
QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordUnlocked));
|
|
QVERIFY(fileRecordUnlocked._lockstate._locked);
|
|
}
|
|
|
|
void testSyncLockedFiles()
|
|
{
|
|
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
int nGET = 0, nPUT = 0;
|
|
QObject parent;
|
|
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) -> QNetworkReply * {
|
|
Q_UNUSED(outgoingData)
|
|
Q_UNUSED(request)
|
|
|
|
if (op == QNetworkAccessManager::PutOperation) {
|
|
++nPUT;
|
|
} else if (op == QNetworkAccessManager::GetOperation) {
|
|
++nGET;
|
|
}
|
|
|
|
return nullptr;
|
|
});
|
|
|
|
ItemCompletedSpy completeSpy(fakeFolder);
|
|
|
|
completeSpy.clear();
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
QCOMPARE(nGET, 0);
|
|
QCOMPARE(nPUT, 0);
|
|
|
|
QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::UnlockedItem);
|
|
OCC::SyncJournalFileRecord fileRecordBefore;
|
|
QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordBefore));
|
|
QVERIFY(!fileRecordBefore._lockstate._locked);
|
|
|
|
fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileLocked, 1, QStringLiteral("Nextcloud Office"), {}, QStringLiteral("richdocuments"), QDateTime::currentDateTime().toSecsSinceEpoch(), 1226);
|
|
fakeFolder.remoteModifier().setModTimeKeepEtag(QStringLiteral("A/a1"), QDateTime::currentDateTime());
|
|
fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1"));
|
|
|
|
completeSpy.clear();
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
QCOMPARE(nGET, 1);
|
|
QCOMPARE(nPUT, 0);
|
|
|
|
QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::LockedItem);
|
|
OCC::SyncJournalFileRecord fileRecordLocked;
|
|
QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordLocked));
|
|
QVERIFY(fileRecordLocked._lockstate._locked);
|
|
|
|
completeSpy.clear();
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
QCOMPARE(nGET, 1);
|
|
QCOMPARE(nPUT, 0);
|
|
|
|
OCC::SyncJournalFileRecord fileRecordAfter;
|
|
QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordAfter));
|
|
QVERIFY(fileRecordAfter._lockstate._locked);
|
|
|
|
auto expectedState = fakeFolder.currentLocalState();
|
|
QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
|
|
}
|
|
|
|
void testLockFile_lockedFileReadOnly_afterSync()
|
|
{
|
|
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
ItemCompletedSpy completeSpy(fakeFolder);
|
|
|
|
completeSpy.clear();
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::UnlockedItem);
|
|
OCC::SyncJournalFileRecord fileRecordBefore;
|
|
QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordBefore));
|
|
QVERIFY(!fileRecordBefore._lockstate._locked);
|
|
|
|
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
|
|
|
|
const auto localFileNotLocked = QFileInfo{fakeFolder.localPath() + u"A/a1"};
|
|
QVERIFY(localFileNotLocked.isWritable());
|
|
|
|
fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileLocked, 1, QStringLiteral("Nextcloud Office"), {}, QStringLiteral("richdocuments"), QDateTime::currentDateTime().toSecsSinceEpoch(), 1226);
|
|
fakeFolder.remoteModifier().setModTimeKeepEtag(QStringLiteral("A/a1"), QDateTime::currentDateTime());
|
|
fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1"));
|
|
|
|
completeSpy.clear();
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::LockedItem);
|
|
OCC::SyncJournalFileRecord fileRecordLocked;
|
|
QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordLocked));
|
|
QVERIFY(fileRecordLocked._lockstate._locked);
|
|
|
|
auto expectedState = fakeFolder.currentLocalState();
|
|
QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
|
|
|
|
const auto localFileLocked = QFileInfo{fakeFolder.localPath() + u"A/a1"};
|
|
QVERIFY(!localFileLocked.isWritable());
|
|
}
|
|
|
|
void testLockFile_lockFile_detect_newly_uploaded()
|
|
{
|
|
const auto testFileName = QStringLiteral("document.docx");
|
|
const auto testLockFileName = QStringLiteral(".~lock.document.docx#");
|
|
|
|
const auto testDocumentsDirName = "documents";
|
|
|
|
FakeFolder fakeFolder{FileInfo{}};
|
|
fakeFolder.localModifier().mkdir(testDocumentsDirName);
|
|
|
|
fakeFolder.syncEngine().account()->setCapabilities({{"files", QVariantMap{{"locking", QByteArray{"1.0"}}}}});
|
|
QSignalSpy lockFileDetectedNewlyUploadedSpy(&fakeFolder.syncEngine(), &OCC::SyncEngine::lockFileDetected);
|
|
|
|
fakeFolder.localModifier().insert(testDocumentsDirName + QString("/") + testLockFileName);
|
|
fakeFolder.localModifier().insert(testDocumentsDirName + QString("/") + testFileName);
|
|
|
|
QVERIFY(fakeFolder.syncOnce());
|
|
|
|
QCOMPARE(lockFileDetectedNewlyUploadedSpy.count(), 1);
|
|
}
|
|
};
|
|
|
|
QTEST_GUILESS_MAIN(TestLockFile)
|
|
#include "testlockfile.moc"
|