1 /* 2 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 3 SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org> 4 */ 5 6 #include <utility> 7 8 #include <QProcess> 9 #include <QTest> 10 11 #include <KIO/Job> 12 13 #include "transfer_resume.h" 14 15 struct FakeWorker { 16 struct FakeError { 17 const int id; 18 const QString message; 19 }; 20 errorFakeWorker21 void error(int id, const QString &message) 22 { 23 m_errors.push_back(FakeError{id, message}); 24 } 25 configValueFakeWorker26 bool configValue(const QString &key, bool defaultValue) const 27 { 28 return m_config.value(key, defaultValue); 29 } 30 canResumeFakeWorker31 bool canResume(KIO::filesize_t offset) 32 { 33 Q_UNUSED(offset); // in reality this is a user query, always assume the user says yes for now 34 return true; 35 } 36 debugErrorsFakeWorker37 void debugErrors() 38 { 39 for (const auto &error : std::as_const(m_errors)) { 40 qDebug() << "ERROR{" << KIO::buildErrorString(error.id, error.message) << "}"; 41 } 42 } 43 44 QVector<FakeError> m_errors; 45 QHash<QString, bool> m_config = {{"MarkPartial", true}}; 46 }; 47 48 class ShouldResumeTest : public QObject 49 { 50 Q_OBJECT 51 private: 52 QTemporaryDir m_tmpDir; 53 private Q_SLOTS: initTestCase()54 void initTestCase() 55 { 56 QVERIFY(m_tmpDir.isValid()); 57 const QString fixturesPath = QFINDTESTDATA("fixtures/."); 58 QProcess proc; 59 proc.setProcessChannelMode(QProcess::ForwardedChannels); 60 proc.start("cp", {"-rv", fixturesPath, m_tmpDir.path()}); 61 QVERIFY(proc.waitForFinished()); 62 QCOMPARE(proc.exitCode(), 0); 63 } 64 tmpPath(const QString & subpath)65 QString tmpPath(const QString &subpath) 66 { 67 return QDir(m_tmpDir.path()).filePath(subpath); 68 } 69 tmpUrl(const QString & subpath)70 QUrl tmpUrl(const QString &subpath) 71 { 72 return QUrl::fromLocalFile(tmpPath(subpath)); 73 } 74 noResumeButPartial()75 void noResumeButPartial() 76 { 77 // NB: this has no fixture ;) 78 FakeWorker worker; 79 80 auto url = tmpUrl("noResumeButPartial/thing"); 81 auto partUrl = tmpUrl("noResumeButPartial/thing.part"); 82 auto resume = Transfer::shouldResume<QFileResumeIO>(url, KIO::JobFlags(), &worker); 83 QVERIFY(resume.has_value()); 84 QCOMPARE(resume->resuming, false); 85 QCOMPARE(resume->destination, partUrl); 86 QCOMPARE(resume->completeDestination, url); 87 QCOMPARE(resume->partDestination, partUrl); 88 89 QDir().mkdir(tmpPath("noResumeButPartial")); 90 QFile part(partUrl.toLocalFile()); 91 QVERIFY(part.open(QFile::WriteOnly)); 92 part.write(""); 93 QCOMPARE(Transfer::concludeResumeHasError<QFileResumeIO>(false, resume.value(), &worker), false); 94 QVERIFY(QFileInfo::exists(url.toLocalFile())); 95 QVERIFY(!QFileInfo::exists(partUrl.toLocalFile())); 96 } 97 noResumeAndNoPartial()98 void noResumeAndNoPartial() 99 { 100 // NB: this has no fixture ;) 101 FakeWorker worker; 102 worker.m_config["MarkPartial"] = false; 103 104 105 auto url = tmpUrl("noResumeAndNoPartial/thing"); 106 auto resume = Transfer::shouldResume<QFileResumeIO>(url, KIO::JobFlags(), &worker); 107 worker.debugErrors(); 108 QVERIFY(resume.has_value()); 109 QCOMPARE(resume->resuming, false); 110 QCOMPARE(resume->destination, url); 111 QCOMPARE(resume->completeDestination, url); 112 QCOMPARE(resume->partDestination, QUrl()); 113 QCOMPARE(Transfer::concludeResumeHasError<QFileResumeIO>(false, resume.value(), &worker), false); 114 } 115 resume()116 void resume() 117 { 118 FakeWorker worker; 119 120 auto url = tmpUrl("resume/thing"); 121 auto partUrl = tmpUrl("resume/thing.part"); 122 auto resume = Transfer::shouldResume<QFileResumeIO>(url, KIO::JobFlags(), &worker); 123 QVERIFY(resume.has_value()); 124 QCOMPARE(resume->resuming, true); 125 QCOMPARE(resume->destination, partUrl); 126 QCOMPARE(resume->completeDestination, url); 127 QCOMPARE(resume->partDestination, partUrl); 128 129 QCOMPARE(Transfer::concludeResumeHasError<QFileResumeIO>(false, resume.value(), &worker), false); 130 QVERIFY(QFileInfo::exists(url.toLocalFile())); 131 QVERIFY(!QFileInfo::exists(partUrl.toLocalFile())); 132 } 133 resumeInPlace()134 void resumeInPlace() 135 { 136 FakeWorker worker; 137 138 auto url = tmpUrl("resumeInPlace/thing"); 139 auto resume = Transfer::shouldResume<QFileResumeIO>(url, KIO::Resume, &worker); 140 QVERIFY(resume.has_value()); 141 QCOMPARE(resume->resuming, true); 142 QCOMPARE(resume->destination, url); 143 QCOMPARE(resume->completeDestination, url); 144 QCOMPARE(resume->partDestination, url); 145 146 QCOMPARE(Transfer::concludeResumeHasError<QFileResumeIO>(false, resume.value(), &worker), false); 147 QVERIFY(QFileInfo::exists(url.toLocalFile())); 148 } 149 noResumeInPlace()150 void noResumeInPlace() 151 { 152 FakeWorker worker; 153 154 auto url = tmpUrl("resumeInPlace/thing"); // intentionally the same path this scenario errors out 155 auto resume = Transfer::shouldResume<QFileResumeIO>(url, KIO::JobFlags(), &worker); 156 QVERIFY(!resume.has_value()); 157 QCOMPARE(worker.m_errors.size(), 1); 158 QCOMPARE(worker.m_errors.at(0).id, KIO::ERR_FILE_ALREADY_EXIST); 159 } 160 }; 161 162 QTEST_GUILESS_MAIN(ShouldResumeTest) 163 164 #include "shouldresumetest.moc" 165