testlockfile.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. #include "lockfilejobs.h"
  2. #include "account.h"
  3. #include "accountstate.h"
  4. #include "common/syncjournaldb.h"
  5. #include "common/syncjournalfilerecord.h"
  6. #include "syncenginetestutils.h"
  7. #include "localdiscoverytracker.h"
  8. #include <QTest>
  9. #include <QSignalSpy>
  10. class TestLockFile : public QObject
  11. {
  12. Q_OBJECT
  13. public:
  14. TestLockFile() = default;
  15. private slots:
  16. void initTestCase()
  17. {
  18. }
  19. void testLockFile_lockFile_lockSuccess()
  20. {
  21. const auto testFileName = QStringLiteral("file.txt");
  22. FakeFolder fakeFolder{FileInfo{}};
  23. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  24. QSignalSpy lockFileSuccessSpy(fakeFolder.account().data(), &OCC::Account::lockFileSuccess);
  25. QSignalSpy lockFileErrorSpy(fakeFolder.account().data(), &OCC::Account::lockFileError);
  26. fakeFolder.localModifier().insert(testFileName);
  27. QVERIFY(fakeFolder.syncOnce());
  28. fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName, &fakeFolder.syncJournal(), OCC::SyncFileItem::LockStatus::LockedItem);
  29. QVERIFY(lockFileSuccessSpy.wait());
  30. QCOMPARE(lockFileErrorSpy.count(), 0);
  31. }
  32. void testLockFile_lockFile_lockError()
  33. {
  34. const auto testFileName = QStringLiteral("file.txt");
  35. static constexpr auto LockedHttpErrorCode = 423;
  36. const auto replyData = QByteArray("<?xml version=\"1.0\"?>\n"
  37. "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
  38. " <nc:lock/>\n"
  39. " <nc:lock-owner-type>0</nc:lock-owner-type>\n"
  40. " <nc:lock-owner>john</nc:lock-owner>\n"
  41. " <nc:lock-owner-displayname>John Doe</nc:lock-owner-displayname>\n"
  42. " <nc:lock-owner-editor>john</nc:lock-owner-editor>\n"
  43. " <nc:lock-time>1650619678</nc:lock-time>\n"
  44. " <nc:lock-timeout>300</nc:lock-timeout>\n"
  45. " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
  46. "</d:prop>\n");
  47. FakeFolder fakeFolder{FileInfo{}};
  48. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  49. fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) {
  50. QNetworkReply *reply = nullptr;
  51. if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) {
  52. reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData);
  53. }
  54. return reply;
  55. });
  56. QSignalSpy lockFileSuccessSpy(fakeFolder.account().data(), &OCC::Account::lockFileSuccess);
  57. QSignalSpy lockFileErrorSpy(fakeFolder.account().data(), &OCC::Account::lockFileError);
  58. fakeFolder.localModifier().insert(testFileName);
  59. QVERIFY(fakeFolder.syncOnce());
  60. fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName, &fakeFolder.syncJournal(), OCC::SyncFileItem::LockStatus::LockedItem);
  61. QVERIFY(lockFileErrorSpy.wait());
  62. QCOMPARE(lockFileSuccessSpy.count(), 0);
  63. }
  64. void testLockFile_fileLockStatus_queryLockStatus()
  65. {
  66. const auto testFileName = QStringLiteral("file.txt");
  67. FakeFolder fakeFolder{FileInfo{}};
  68. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  69. QSignalSpy lockFileSuccessSpy(fakeFolder.account().data(), &OCC::Account::lockFileSuccess);
  70. QSignalSpy lockFileErrorSpy(fakeFolder.account().data(), &OCC::Account::lockFileError);
  71. fakeFolder.localModifier().insert(testFileName);
  72. QVERIFY(fakeFolder.syncOnce());
  73. fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName, &fakeFolder.syncJournal(), OCC::SyncFileItem::LockStatus::LockedItem);
  74. QVERIFY(lockFileSuccessSpy.wait());
  75. QCOMPARE(lockFileErrorSpy.count(), 0);
  76. auto lockStatus = fakeFolder.account()->fileLockStatus(&fakeFolder.syncJournal(), testFileName);
  77. QCOMPARE(lockStatus, OCC::SyncFileItem::LockStatus::LockedItem);
  78. }
  79. void testLockFile_fileCanBeUnlocked_canUnlock()
  80. {
  81. const auto testFileName = QStringLiteral("file.txt");
  82. FakeFolder fakeFolder{FileInfo{}};
  83. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  84. QSignalSpy lockFileSuccessSpy(fakeFolder.account().data(), &OCC::Account::lockFileSuccess);
  85. QSignalSpy lockFileErrorSpy(fakeFolder.account().data(), &OCC::Account::lockFileError);
  86. fakeFolder.localModifier().insert(testFileName);
  87. QVERIFY(fakeFolder.syncOnce());
  88. fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName, &fakeFolder.syncJournal(), OCC::SyncFileItem::LockStatus::LockedItem);
  89. QVERIFY(lockFileSuccessSpy.wait());
  90. QCOMPARE(lockFileErrorSpy.count(), 0);
  91. auto lockStatus = fakeFolder.account()->fileCanBeUnlocked(&fakeFolder.syncJournal(), testFileName);
  92. QCOMPARE(lockStatus, true);
  93. }
  94. void testLockFile_lockFile_jobSuccess()
  95. {
  96. const auto testFileName = QStringLiteral("file.txt");
  97. FakeFolder fakeFolder{FileInfo{}};
  98. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  99. fakeFolder.localModifier().insert(testFileName);
  100. QVERIFY(fakeFolder.syncOnce());
  101. auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
  102. QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
  103. QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
  104. job->start();
  105. QVERIFY(jobSuccess.wait());
  106. QCOMPARE(jobFailure.count(), 0);
  107. auto fileRecord = OCC::SyncJournalFileRecord{};
  108. QVERIFY(fakeFolder.syncJournal().getFileRecord(testFileName, &fileRecord));
  109. QCOMPARE(fileRecord._lockstate._locked, true);
  110. QCOMPARE(fileRecord._lockstate._lockEditorApp, QString{});
  111. QCOMPARE(fileRecord._lockstate._lockOwnerDisplayName, QStringLiteral("John Doe"));
  112. QCOMPARE(fileRecord._lockstate._lockOwnerId, QStringLiteral("admin"));
  113. QCOMPARE(fileRecord._lockstate._lockOwnerType, static_cast<qint64>(OCC::SyncFileItem::LockOwnerType::UserLock));
  114. QCOMPARE(fileRecord._lockstate._lockTime, 1234560);
  115. QCOMPARE(fileRecord._lockstate._lockTimeout, 1800);
  116. QVERIFY(fakeFolder.syncOnce());
  117. }
  118. void testLockFile_lockFile_unlockFile_jobSuccess()
  119. {
  120. const auto testFileName = QStringLiteral("file.txt");
  121. FakeFolder fakeFolder{FileInfo{}};
  122. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  123. fakeFolder.localModifier().insert(testFileName);
  124. QVERIFY(fakeFolder.syncOnce());
  125. auto lockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
  126. QSignalSpy lockFileJobSuccess(lockFileJob, &OCC::LockFileJob::finishedWithoutError);
  127. QSignalSpy lockFileJobFailure(lockFileJob, &OCC::LockFileJob::finishedWithError);
  128. lockFileJob->start();
  129. QVERIFY(lockFileJobSuccess.wait());
  130. QCOMPARE(lockFileJobFailure.count(), 0);
  131. QVERIFY(fakeFolder.syncOnce());
  132. auto unlockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem);
  133. QSignalSpy unlockFileJobSuccess(unlockFileJob, &OCC::LockFileJob::finishedWithoutError);
  134. QSignalSpy unlockFileJobFailure(unlockFileJob, &OCC::LockFileJob::finishedWithError);
  135. unlockFileJob->start();
  136. QVERIFY(unlockFileJobSuccess.wait());
  137. QCOMPARE(unlockFileJobFailure.count(), 0);
  138. auto fileRecord = OCC::SyncJournalFileRecord{};
  139. QVERIFY(fakeFolder.syncJournal().getFileRecord(testFileName, &fileRecord));
  140. QCOMPARE(fileRecord._lockstate._locked, false);
  141. QVERIFY(fakeFolder.syncOnce());
  142. }
  143. void testLockFile_lockFile_alreadyLockedByUser()
  144. {
  145. static constexpr auto LockedHttpErrorCode = 423;
  146. static constexpr auto PreconditionFailedHttpErrorCode = 412;
  147. const auto testFileName = QStringLiteral("file.txt");
  148. const auto replyData = QByteArray("<?xml version=\"1.0\"?>\n"
  149. "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
  150. " <nc:lock>1</nc:lock>\n"
  151. " <nc:lock-owner-type>0</nc:lock-owner-type>\n"
  152. " <nc:lock-owner>john</nc:lock-owner>\n"
  153. " <nc:lock-owner-displayname>John Doe</nc:lock-owner-displayname>\n"
  154. " <nc:lock-owner-editor>john</nc:lock-owner-editor>\n"
  155. " <nc:lock-time>1650619678</nc:lock-time>\n"
  156. " <nc:lock-timeout>300</nc:lock-timeout>\n"
  157. " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
  158. "</d:prop>\n");
  159. FakeFolder fakeFolder{FileInfo{}};
  160. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  161. fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) {
  162. QNetworkReply *reply = nullptr;
  163. if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) {
  164. reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData);
  165. } else if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK")) {
  166. reply = new FakeErrorReply(op, request, nullptr, PreconditionFailedHttpErrorCode, replyData);
  167. }
  168. return reply;
  169. });
  170. fakeFolder.localModifier().insert(testFileName);
  171. QVERIFY(fakeFolder.syncOnce());
  172. auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
  173. QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
  174. QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
  175. job->start();
  176. QVERIFY(jobFailure.wait());
  177. QCOMPARE(jobSuccess.count(), 0);
  178. }
  179. void testLockFile_lockFile_alreadyLockedByApp()
  180. {
  181. static constexpr auto LockedHttpErrorCode = 423;
  182. static constexpr auto PreconditionFailedHttpErrorCode = 412;
  183. const auto testFileName = QStringLiteral("file.txt");
  184. const auto replyData = QByteArray("<?xml version=\"1.0\"?>\n"
  185. "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
  186. " <nc:lock>1</nc:lock>\n"
  187. " <nc:lock-owner-type>1</nc:lock-owner-type>\n"
  188. " <nc:lock-owner>john</nc:lock-owner>\n"
  189. " <nc:lock-owner-displayname>John Doe</nc:lock-owner-displayname>\n"
  190. " <nc:lock-owner-editor>Text</nc:lock-owner-editor>\n"
  191. " <nc:lock-time>1650619678</nc:lock-time>\n"
  192. " <nc:lock-timeout>300</nc:lock-timeout>\n"
  193. " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
  194. "</d:prop>\n");
  195. FakeFolder fakeFolder{FileInfo{}};
  196. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  197. fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) {
  198. QNetworkReply *reply = nullptr;
  199. if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) {
  200. reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData);
  201. } else if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK")) {
  202. reply = new FakeErrorReply(op, request, nullptr, PreconditionFailedHttpErrorCode, replyData);
  203. }
  204. return reply;
  205. });
  206. fakeFolder.localModifier().insert(testFileName);
  207. QVERIFY(fakeFolder.syncOnce());
  208. auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
  209. QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
  210. QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
  211. job->start();
  212. QVERIFY(jobFailure.wait());
  213. QCOMPARE(jobSuccess.count(), 0);
  214. }
  215. void testLockFile_unlockFile_alreadyUnlocked()
  216. {
  217. static constexpr auto LockedHttpErrorCode = 423;
  218. static constexpr auto PreconditionFailedHttpErrorCode = 412;
  219. const auto testFileName = QStringLiteral("file.txt");
  220. const auto replyData = QByteArray("<?xml version=\"1.0\"?>\n"
  221. "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
  222. " <nc:lock/>\n"
  223. " <nc:lock-owner-type>0</nc:lock-owner-type>\n"
  224. " <nc:lock-owner>john</nc:lock-owner>\n"
  225. " <nc:lock-owner-displayname>John Doe</nc:lock-owner-displayname>\n"
  226. " <nc:lock-owner-editor>john</nc:lock-owner-editor>\n"
  227. " <nc:lock-time>1650619678</nc:lock-time>\n"
  228. " <nc:lock-timeout>300</nc:lock-timeout>\n"
  229. " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
  230. "</d:prop>\n");
  231. FakeFolder fakeFolder{FileInfo{}};
  232. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  233. fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) {
  234. QNetworkReply *reply = nullptr;
  235. if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK")) {
  236. reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData);
  237. } else if (op == QNetworkAccessManager::CustomOperation && request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK")) {
  238. reply = new FakeErrorReply(op, request, nullptr, PreconditionFailedHttpErrorCode, replyData);
  239. }
  240. return reply;
  241. });
  242. fakeFolder.localModifier().insert(testFileName);
  243. QVERIFY(fakeFolder.syncOnce());
  244. auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem);
  245. QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
  246. QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
  247. job->start();
  248. QVERIFY(jobSuccess.wait());
  249. QCOMPARE(jobFailure.count(), 0);
  250. }
  251. void testLockFile_unlockFile_lockedBySomeoneElse()
  252. {
  253. static constexpr auto LockedHttpErrorCode = 423;
  254. const auto testFileName = QStringLiteral("file.txt");
  255. const auto replyData = QByteArray("<?xml version=\"1.0\"?>\n"
  256. "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
  257. " <nc:lock>1</nc:lock>\n"
  258. " <nc:lock-owner-type>0</nc:lock-owner-type>\n"
  259. " <nc:lock-owner>alice</nc:lock-owner>\n"
  260. " <nc:lock-owner-displayname>Alice Doe</nc:lock-owner-displayname>\n"
  261. " <nc:lock-owner-editor>Text</nc:lock-owner-editor>\n"
  262. " <nc:lock-time>1650619678</nc:lock-time>\n"
  263. " <nc:lock-timeout>300</nc:lock-timeout>\n"
  264. " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
  265. "</d:prop>\n");
  266. FakeFolder fakeFolder{FileInfo{}};
  267. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  268. fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) {
  269. QNetworkReply *reply = nullptr;
  270. if (op == QNetworkAccessManager::CustomOperation && (request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK") ||
  271. request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK"))) {
  272. reply = new FakeErrorReply(op, request, nullptr, LockedHttpErrorCode, replyData);
  273. }
  274. return reply;
  275. });
  276. fakeFolder.localModifier().insert(testFileName);
  277. QVERIFY(fakeFolder.syncOnce());
  278. auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem);
  279. QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
  280. QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
  281. job->start();
  282. QVERIFY(jobFailure.wait());
  283. QCOMPARE(jobSuccess.count(), 0);
  284. }
  285. void testLockFile_lockFile_jobError()
  286. {
  287. const auto testFileName = QStringLiteral("file.txt");
  288. static constexpr auto InternalServerErrorHttpErrorCode = 500;
  289. FakeFolder fakeFolder{FileInfo{}};
  290. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  291. fakeFolder.setServerOverride([] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) {
  292. QNetworkReply *reply = nullptr;
  293. if (op == QNetworkAccessManager::CustomOperation && (request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK") ||
  294. request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK"))) {
  295. reply = new FakeErrorReply(op, request, nullptr, InternalServerErrorHttpErrorCode, {});
  296. }
  297. return reply;
  298. });
  299. fakeFolder.localModifier().insert(QStringLiteral("file.txt"));
  300. QVERIFY(fakeFolder.syncOnce());
  301. auto lockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
  302. QSignalSpy lockFileJobSuccess(lockFileJob, &OCC::LockFileJob::finishedWithoutError);
  303. QSignalSpy lockFileJobFailure(lockFileJob, &OCC::LockFileJob::finishedWithError);
  304. lockFileJob->start();
  305. QVERIFY(lockFileJobFailure.wait());
  306. QCOMPARE(lockFileJobSuccess.count(), 0);
  307. QVERIFY(fakeFolder.syncOnce());
  308. auto unlockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem);
  309. QSignalSpy unlockFileJobSuccess(unlockFileJob, &OCC::LockFileJob::finishedWithoutError);
  310. QSignalSpy unlockFileJobFailure(unlockFileJob, &OCC::LockFileJob::finishedWithError);
  311. unlockFileJob->start();
  312. QVERIFY(unlockFileJobFailure.wait());
  313. QCOMPARE(unlockFileJobSuccess.count(), 0);
  314. QVERIFY(fakeFolder.syncOnce());
  315. }
  316. void testLockFile_lockFile_preconditionFailedError()
  317. {
  318. static constexpr auto PreconditionFailedHttpErrorCode = 412;
  319. const auto testFileName = QStringLiteral("file.txt");
  320. const auto replyData = QByteArray("<?xml version=\"1.0\"?>\n"
  321. "<d:prop xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">\n"
  322. " <nc:lock>1</nc:lock>\n"
  323. " <nc:lock-owner-type>0</nc:lock-owner-type>\n"
  324. " <nc:lock-owner>alice</nc:lock-owner>\n"
  325. " <nc:lock-owner-displayname>Alice Doe</nc:lock-owner-displayname>\n"
  326. " <nc:lock-owner-editor>Text</nc:lock-owner-editor>\n"
  327. " <nc:lock-time>1650619678</nc:lock-time>\n"
  328. " <nc:lock-timeout>300</nc:lock-timeout>\n"
  329. " <nc:lock-token>files_lock/310997d7-0aae-4e48-97e1-eeb6be6e2202</nc:lock-token>\n"
  330. "</d:prop>\n");
  331. FakeFolder fakeFolder{FileInfo{}};
  332. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  333. fakeFolder.setServerOverride([replyData] (FakeQNAM::Operation op, const QNetworkRequest &request, QIODevice *) {
  334. QNetworkReply *reply = nullptr;
  335. if (op == QNetworkAccessManager::CustomOperation && (request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("LOCK") ||
  336. request.attribute(QNetworkRequest::CustomVerbAttribute).toString() == QStringLiteral("UNLOCK"))) {
  337. reply = new FakeErrorReply(op, request, nullptr, PreconditionFailedHttpErrorCode, replyData);
  338. }
  339. return reply;
  340. });
  341. fakeFolder.localModifier().insert(testFileName);
  342. QVERIFY(fakeFolder.syncOnce());
  343. auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem);
  344. QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
  345. QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
  346. job->start();
  347. QVERIFY(jobFailure.wait());
  348. QCOMPARE(jobSuccess.count(), 0);
  349. }
  350. void testSyncLockedFilesAlmostExpired()
  351. {
  352. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  353. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  354. QSignalSpy spySyncCompleted(&fakeFolder.syncEngine(), &OCC::SyncEngine::finished);
  355. ItemCompletedSpy completeSpy(fakeFolder);
  356. completeSpy.clear();
  357. QVERIFY(fakeFolder.syncOnce());
  358. QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::UnlockedItem);
  359. OCC::SyncJournalFileRecord fileRecordBefore;
  360. QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordBefore));
  361. QVERIFY(!fileRecordBefore._lockstate._locked);
  362. fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileLocked, 1, QStringLiteral("Nextcloud Office"), {}, QStringLiteral("richdocuments"), QDateTime::currentDateTime().toSecsSinceEpoch() - 1220, 1226);
  363. completeSpy.clear();
  364. QVERIFY(fakeFolder.syncOnce());
  365. QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::LockedItem);
  366. OCC::SyncJournalFileRecord fileRecordLocked;
  367. QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordLocked));
  368. QVERIFY(fileRecordLocked._lockstate._locked);
  369. spySyncCompleted.clear();
  370. QTest::qWait(5000);
  371. fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileUnlocked, {}, {}, {}, {}, {}, {});
  372. QCOMPARE(spySyncCompleted.count(), 0);
  373. QVERIFY(spySyncCompleted.wait(3000));
  374. QCOMPARE(spySyncCompleted.count(), 1);
  375. OCC::SyncJournalFileRecord fileRecordUnlocked;
  376. QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordUnlocked));
  377. QVERIFY(!fileRecordUnlocked._lockstate._locked);
  378. }
  379. void testSyncLockedFilesNoExpiredLockedFiles()
  380. {
  381. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  382. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  383. QSignalSpy spySyncCompleted(&fakeFolder.syncEngine(), &OCC::SyncEngine::finished);
  384. ItemCompletedSpy completeSpy(fakeFolder);
  385. completeSpy.clear();
  386. QVERIFY(fakeFolder.syncOnce());
  387. QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::UnlockedItem);
  388. OCC::SyncJournalFileRecord fileRecordBefore;
  389. QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordBefore));
  390. QVERIFY(!fileRecordBefore._lockstate._locked);
  391. fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileLocked, 1, QStringLiteral("Nextcloud Office"), {}, QStringLiteral("richdocuments"), QDateTime::currentDateTime().toSecsSinceEpoch() - 1220, 1226);
  392. completeSpy.clear();
  393. QVERIFY(fakeFolder.syncOnce());
  394. QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::LockedItem);
  395. OCC::SyncJournalFileRecord fileRecordLocked;
  396. QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordLocked));
  397. QVERIFY(fileRecordLocked._lockstate._locked);
  398. completeSpy.clear();
  399. QVERIFY(fakeFolder.syncOnce());
  400. spySyncCompleted.clear();
  401. QTest::qWait(1000);
  402. fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileUnlocked, {}, {}, {}, {}, {}, {});
  403. QCOMPARE(spySyncCompleted.count(), 0);
  404. QVERIFY(!spySyncCompleted.wait(3000));
  405. QCOMPARE(spySyncCompleted.count(), 0);
  406. OCC::SyncJournalFileRecord fileRecordUnlocked;
  407. QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordUnlocked));
  408. QVERIFY(fileRecordUnlocked._lockstate._locked);
  409. }
  410. void testSyncLockedFiles()
  411. {
  412. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  413. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  414. int nGET = 0, nPUT = 0;
  415. QObject parent;
  416. fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) -> QNetworkReply * {
  417. Q_UNUSED(outgoingData)
  418. Q_UNUSED(request)
  419. if (op == QNetworkAccessManager::PutOperation) {
  420. ++nPUT;
  421. } else if (op == QNetworkAccessManager::GetOperation) {
  422. ++nGET;
  423. }
  424. return nullptr;
  425. });
  426. ItemCompletedSpy completeSpy(fakeFolder);
  427. completeSpy.clear();
  428. QVERIFY(fakeFolder.syncOnce());
  429. QCOMPARE(nGET, 0);
  430. QCOMPARE(nPUT, 0);
  431. QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::UnlockedItem);
  432. OCC::SyncJournalFileRecord fileRecordBefore;
  433. QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordBefore));
  434. QVERIFY(!fileRecordBefore._lockstate._locked);
  435. fakeFolder.remoteModifier().modifyLockState(QStringLiteral("A/a1"), FileModifier::LockState::FileLocked, 1, QStringLiteral("Nextcloud Office"), {}, QStringLiteral("richdocuments"), QDateTime::currentDateTime().toSecsSinceEpoch(), 1226);
  436. fakeFolder.remoteModifier().setModTimeKeepEtag(QStringLiteral("A/a1"), QDateTime::currentDateTime());
  437. fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1"));
  438. completeSpy.clear();
  439. QVERIFY(fakeFolder.syncOnce());
  440. QCOMPARE(nGET, 1);
  441. QCOMPARE(nPUT, 0);
  442. QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1"))->_locked, OCC::SyncFileItem::LockStatus::LockedItem);
  443. OCC::SyncJournalFileRecord fileRecordLocked;
  444. QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordLocked));
  445. QVERIFY(fileRecordLocked._lockstate._locked);
  446. completeSpy.clear();
  447. QVERIFY(fakeFolder.syncOnce());
  448. QCOMPARE(nGET, 1);
  449. QCOMPARE(nPUT, 0);
  450. OCC::SyncJournalFileRecord fileRecordAfter;
  451. QVERIFY(fakeFolder.syncJournal().getFileRecord(QStringLiteral("A/a1"), &fileRecordAfter));
  452. QVERIFY(fileRecordAfter._lockstate._locked);
  453. auto expectedState = fakeFolder.currentLocalState();
  454. QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
  455. }
  456. };
  457. QTEST_GUILESS_MAIN(TestLockFile)
  458. #include "testlockfile.moc"