File indexing completed on 2024-05-12 05:50:10
0001 /* 0002 SPDX-FileCopyrightText: 2010-2011 Raphael Kubo da Costa <rakuco@FreeBSD.org> 0003 SPDX-FileCopyrightText: 2016 Elvis Angelaccio <elvis.angelaccio@kde.org> 0004 0005 SPDX-License-Identifier: BSD-2-Clause 0006 */ 0007 0008 #include "jobs.h" 0009 #include "jsonarchiveinterface.h" 0010 0011 #include <KPluginMetaData> 0012 0013 #include <QDebug> 0014 #include <QEventLoop> 0015 #include <QTest> 0016 0017 using namespace Kerfuffle; 0018 0019 class JobsTest : public QObject 0020 { 0021 Q_OBJECT 0022 0023 public: 0024 JobsTest(); 0025 0026 protected Q_SLOTS: 0027 void init(); 0028 void slotNewEntry(Archive::Entry *entry); 0029 0030 private Q_SLOTS: 0031 // ListJob-related tests 0032 void testLoadJob_data(); 0033 void testLoadJob(); 0034 0035 // ExtractJob-related tests 0036 void testExtractJobAccessors(); 0037 void testTempExtractJob(); 0038 0039 // DeleteJob-related tests 0040 void testRemoveEntries_data(); 0041 void testRemoveEntries(); 0042 0043 // AddJob-related tests 0044 void testAddEntries_data(); 0045 void testAddEntries(); 0046 0047 private: 0048 JSONArchiveInterface *createArchiveInterface(const QString &filePath); 0049 QVector<Archive::Entry *> listEntries(JSONArchiveInterface *iface); 0050 void startAndWaitForResult(KJob *job); 0051 0052 QVector<Archive::Entry *> m_entries; 0053 QEventLoop m_eventLoop; 0054 }; 0055 0056 QTEST_GUILESS_MAIN(JobsTest) 0057 0058 JobsTest::JobsTest() 0059 : QObject(nullptr) 0060 , m_eventLoop(this) 0061 { 0062 } 0063 0064 void JobsTest::init() 0065 { 0066 m_entries.clear(); 0067 } 0068 0069 void JobsTest::slotNewEntry(Archive::Entry *entry) 0070 { 0071 m_entries.append(entry); 0072 } 0073 0074 JSONArchiveInterface *JobsTest::createArchiveInterface(const QString &filePath) 0075 { 0076 JSONArchiveInterface *iface = new JSONArchiveInterface(this, {filePath, QVariant().fromValue(KPluginMetaData())}); 0077 if (!iface->open()) { 0078 qDebug() << "Could not open" << filePath; 0079 return nullptr; 0080 } 0081 0082 return iface; 0083 } 0084 0085 QVector<Archive::Entry *> JobsTest::listEntries(JSONArchiveInterface *iface) 0086 { 0087 m_entries.clear(); 0088 0089 auto job = new LoadJob(iface); 0090 connect(job, &Job::newEntry, this, &JobsTest::slotNewEntry); 0091 0092 startAndWaitForResult(job); 0093 0094 return m_entries; 0095 } 0096 0097 void JobsTest::startAndWaitForResult(KJob *job) 0098 { 0099 connect(job, &KJob::result, &m_eventLoop, &QEventLoop::quit); 0100 job->start(); 0101 m_eventLoop.exec(); 0102 } 0103 0104 void JobsTest::testLoadJob_data() 0105 { 0106 QTest::addColumn<QString>("jsonArchive"); 0107 QTest::addColumn<qlonglong>("expectedExtractedFilesSize"); 0108 QTest::addColumn<bool>("isPasswordProtected"); 0109 QTest::addColumn<bool>("isSingleFolder"); 0110 QTest::addColumn<QStringList>("expectedEntryNames"); 0111 0112 QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json") << 0LL << false << false 0113 << QStringList{QStringLiteral("a.txt"), QStringLiteral("aDir/"), QStringLiteral("aDir/b.txt"), QStringLiteral("c.txt")}; 0114 0115 QTest::newRow("archive002.json") << QFINDTESTDATA("data/archive002.json") << 45959LL << false << false 0116 << QStringList{QStringLiteral("a.txt"), QStringLiteral("aDir/"), QStringLiteral("aDir/b.txt"), QStringLiteral("c.txt")}; 0117 0118 QTest::newRow("archive-deepsinglehierarchy.json") << QFINDTESTDATA("data/archive-deepsinglehierarchy.json") << 0LL << false << true 0119 << QStringList{ 0120 // Depth-first order! 0121 QStringLiteral("aDir/"), 0122 QStringLiteral("aDir/aDirInside/"), 0123 QStringLiteral("aDir/aDirInside/anotherDir/"), 0124 QStringLiteral("aDir/aDirInside/anotherDir/file.txt"), 0125 QStringLiteral("aDir/b.txt"), 0126 }; 0127 0128 QTest::newRow("archive-multiplefolders.json") << QFINDTESTDATA("data/archive-multiplefolders.json") << 0LL << false << false 0129 << QStringList{ 0130 QStringLiteral("aDir/"), 0131 QStringLiteral("aDir/b.txt"), 0132 QStringLiteral("anotherDir/"), 0133 QStringLiteral("anotherDir/file.txt"), 0134 }; 0135 0136 QTest::newRow("archive-nodir-manyfiles.json") << QFINDTESTDATA("data/archive-nodir-manyfiles.json") << 0LL << false << false 0137 << QStringList{QStringLiteral("a.txt"), QStringLiteral("file.txt")}; 0138 0139 QTest::newRow("archive-onetopfolder.json") << QFINDTESTDATA("data/archive-onetopfolder.json") << 0LL << false << true 0140 << QStringList{QStringLiteral("aDir/"), QStringLiteral("aDir/b.txt")}; 0141 0142 QTest::newRow("archive-password.json") << QFINDTESTDATA("data/archive-password.json") << 0LL << true 0143 << false 0144 // Possibly unexpected behavior of listing: 0145 // 1. Directories are listed before files, if they are empty! 0146 // 2. Files are sorted alphabetically. 0147 << QStringList{QStringLiteral("aDirectory/"), QStringLiteral("bar.txt"), QStringLiteral("foo.txt")}; 0148 0149 QTest::newRow("archive-singlefile.json") << QFINDTESTDATA("data/archive-singlefile.json") << 0LL << false << false << QStringList{QStringLiteral("a.txt")}; 0150 0151 QTest::newRow("archive-emptysinglefolder.json") << QFINDTESTDATA("data/archive-emptysinglefolder.json") << 0LL << false << true 0152 << QStringList{QStringLiteral("aDir/")}; 0153 0154 QTest::newRow("archive-unorderedsinglefolder.json") << QFINDTESTDATA("data/archive-unorderedsinglefolder.json") << 0LL << false << true 0155 << QStringList{ 0156 QStringLiteral("aDir/"), 0157 QStringLiteral("aDir/anotherDir/"), 0158 QStringLiteral("aDir/anotherDir/bar.txt"), 0159 QStringLiteral("aDir/foo.txt"), 0160 }; 0161 } 0162 0163 void JobsTest::testLoadJob() 0164 { 0165 QFETCH(QString, jsonArchive); 0166 JSONArchiveInterface *iface = createArchiveInterface(jsonArchive); 0167 QVERIFY(iface); 0168 0169 auto loadJob = new LoadJob(iface); 0170 loadJob->setAutoDelete(false); 0171 startAndWaitForResult(loadJob); 0172 0173 QFETCH(qlonglong, expectedExtractedFilesSize); 0174 QCOMPARE(loadJob->extractedFilesSize(), expectedExtractedFilesSize); 0175 0176 QFETCH(bool, isPasswordProtected); 0177 QCOMPARE(loadJob->isPasswordProtected(), isPasswordProtected); 0178 0179 QFETCH(bool, isSingleFolder); 0180 QCOMPARE(loadJob->isSingleFolderArchive(), isSingleFolder); 0181 0182 QFETCH(QStringList, expectedEntryNames); 0183 auto archiveEntries = listEntries(iface); 0184 0185 QCOMPARE(archiveEntries.size(), expectedEntryNames.size()); 0186 0187 for (int i = 0; i < archiveEntries.size(); i++) { 0188 QCOMPARE(archiveEntries.at(i)->fullPath(), expectedEntryNames.at(i)); 0189 } 0190 0191 loadJob->deleteLater(); 0192 } 0193 0194 void JobsTest::testExtractJobAccessors() 0195 { 0196 JSONArchiveInterface *iface = createArchiveInterface(QFINDTESTDATA("data/archive001.json")); 0197 ExtractJob *job = new ExtractJob(QVector<Archive::Entry *>(), QStringLiteral("/tmp/some-dir"), ExtractionOptions(), iface); 0198 0199 QCOMPARE(job->destinationDirectory(), QLatin1String("/tmp/some-dir")); 0200 QVERIFY(job->extractionOptions().preservePaths()); 0201 0202 job->setAutoDelete(false); 0203 startAndWaitForResult(job); 0204 0205 QCOMPARE(job->destinationDirectory(), QLatin1String("/tmp/some-dir")); 0206 delete job; 0207 0208 ExtractionOptions options; 0209 options.setPreservePaths(false); 0210 0211 job = new ExtractJob(QVector<Archive::Entry *>(), QStringLiteral("/root"), options, iface); 0212 0213 QCOMPARE(job->destinationDirectory(), QLatin1String("/root")); 0214 QVERIFY(!job->extractionOptions().preservePaths()); 0215 0216 job->setAutoDelete(false); 0217 startAndWaitForResult(job); 0218 0219 QCOMPARE(job->destinationDirectory(), QLatin1String("/root")); 0220 QVERIFY(!job->extractionOptions().preservePaths()); 0221 delete job; 0222 } 0223 0224 void JobsTest::testTempExtractJob() 0225 { 0226 JSONArchiveInterface *iface = createArchiveInterface(QFINDTESTDATA("data/archive-malicious.json")); 0227 PreviewJob *job = new PreviewJob(new Archive::Entry(this, QStringLiteral("anotherDir/../../file.txt")), false, iface); 0228 0229 const QString tempDirPath = job->tempDir()->path(); 0230 QVERIFY(QFileInfo::exists(tempDirPath)); 0231 QVERIFY(job->validatedFilePath().endsWith(QLatin1String("anotherDir/file.txt"))); 0232 QVERIFY(job->extractionOptions().preservePaths()); 0233 0234 job->setAutoDelete(false); 0235 startAndWaitForResult(job); 0236 0237 QVERIFY(job->validatedFilePath().endsWith(QLatin1String("anotherDir/file.txt"))); 0238 QVERIFY(job->extractionOptions().preservePaths()); 0239 0240 delete job->tempDir(); 0241 QVERIFY(!QFileInfo::exists(tempDirPath)); 0242 0243 delete job; 0244 } 0245 0246 void JobsTest::testRemoveEntries_data() 0247 { 0248 QTest::addColumn<QString>("jsonArchive"); 0249 QTest::addColumn<QVector<Archive::Entry *>>("entries"); 0250 QTest::addColumn<QVector<Archive::Entry *>>("entriesToDelete"); 0251 0252 QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json") 0253 << (QVector<Archive::Entry *>{ 0254 new Archive::Entry(this, QStringLiteral("a.txt")), 0255 new Archive::Entry(this, QStringLiteral("aDir/")), 0256 new Archive::Entry(this, QStringLiteral("aDir/b.txt")), 0257 new Archive::Entry(this, QStringLiteral("c.txt")), 0258 }) 0259 << QVector<Archive::Entry *>{ 0260 new Archive::Entry(this, QStringLiteral("c.txt")), 0261 }; 0262 0263 QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json") 0264 << (QVector<Archive::Entry *>{ 0265 new Archive::Entry(this, QStringLiteral("a.txt")), 0266 new Archive::Entry(this, QStringLiteral("aDir/")), 0267 new Archive::Entry(this, QStringLiteral("aDir/b.txt")), 0268 new Archive::Entry(this, QStringLiteral("c.txt")), 0269 }) 0270 << QVector<Archive::Entry *>{ 0271 new Archive::Entry(this, QStringLiteral("a.txt")), 0272 new Archive::Entry(this, QStringLiteral("c.txt")), 0273 }; 0274 0275 // Error test: if we delete non-existent entries, the archive must not change. 0276 QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json") 0277 << (QVector<Archive::Entry *>{ 0278 new Archive::Entry(this, QStringLiteral("a.txt")), 0279 new Archive::Entry(this, QStringLiteral("aDir/")), 0280 new Archive::Entry(this, QStringLiteral("aDir/b.txt")), 0281 new Archive::Entry(this, QStringLiteral("c.txt")), 0282 }) 0283 << QVector<Archive::Entry *>{new Archive::Entry(this, QStringLiteral("foo.txt"))}; 0284 } 0285 0286 void JobsTest::testRemoveEntries() 0287 { 0288 QFETCH(QString, jsonArchive); 0289 JSONArchiveInterface *iface = createArchiveInterface(jsonArchive); 0290 QVERIFY(iface); 0291 0292 QFETCH(QVector<Archive::Entry *>, entries); 0293 QFETCH(QVector<Archive::Entry *>, entriesToDelete); 0294 QStringList fullPathsToDelete = iface->entryFullPaths(entriesToDelete); 0295 0296 QVector<Archive::Entry *> expectedRemainingEntries; 0297 for (Archive::Entry *entry : std::as_const(entries)) { 0298 if (!fullPathsToDelete.contains(entry->fullPath())) { 0299 expectedRemainingEntries.append(entry); 0300 } 0301 } 0302 0303 DeleteJob *deleteJob = new DeleteJob(entriesToDelete, iface); 0304 startAndWaitForResult(deleteJob); 0305 0306 auto remainingEntries = listEntries(iface); 0307 QCOMPARE(remainingEntries.size(), expectedRemainingEntries.size()); 0308 0309 for (int i = 0; i < remainingEntries.size(); i++) { 0310 QCOMPARE(*remainingEntries.at(i), *expectedRemainingEntries.at(i)); 0311 } 0312 0313 iface->deleteLater(); 0314 } 0315 0316 void JobsTest::testAddEntries_data() 0317 { 0318 QTest::addColumn<QString>("jsonArchive"); 0319 QTest::addColumn<QVector<Archive::Entry *>>("originalEntries"); 0320 QTest::addColumn<QVector<Archive::Entry *>>("entriesToAdd"); 0321 QTest::addColumn<Archive::Entry *>("destinationEntry"); 0322 0323 QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json") 0324 << (QVector<Archive::Entry *>{ 0325 new Archive::Entry(this, QStringLiteral("a.txt")), 0326 new Archive::Entry(this, QStringLiteral("aDir/")), 0327 new Archive::Entry(this, QStringLiteral("aDir/b.txt")), 0328 new Archive::Entry(this, QStringLiteral("c.txt")), 0329 }) 0330 << (QVector<Archive::Entry *>{ 0331 new Archive::Entry(this, QStringLiteral("foo.txt")), 0332 }) 0333 << new Archive::Entry(this); 0334 0335 QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json") 0336 << (QVector<Archive::Entry *>{ 0337 new Archive::Entry(this, QStringLiteral("a.txt")), 0338 new Archive::Entry(this, QStringLiteral("aDir/")), 0339 new Archive::Entry(this, QStringLiteral("aDir/b.txt")), 0340 new Archive::Entry(this, QStringLiteral("c.txt")), 0341 }) 0342 << (QVector<Archive::Entry *>{ 0343 new Archive::Entry(this, QStringLiteral("foo.txt")), 0344 new Archive::Entry(this, QStringLiteral("bar.txt")), 0345 }) 0346 << new Archive::Entry(this); 0347 0348 QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json") 0349 << (QVector<Archive::Entry *>{ 0350 new Archive::Entry(this, QStringLiteral("a.txt")), 0351 new Archive::Entry(this, QStringLiteral("aDir/")), 0352 new Archive::Entry(this, QStringLiteral("aDir/b.txt")), 0353 new Archive::Entry(this, QStringLiteral("c.txt")), 0354 }) 0355 << (QVector<Archive::Entry *>{ 0356 new Archive::Entry(this, QStringLiteral("foo.txt")), 0357 new Archive::Entry(this, QStringLiteral("bar.txt")), 0358 }) 0359 << new Archive::Entry(this, QStringLiteral("aDir/")); 0360 0361 QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json") 0362 << (QVector<Archive::Entry *>{ 0363 new Archive::Entry(this, QStringLiteral("a.txt")), 0364 new Archive::Entry(this, QStringLiteral("aDir/")), 0365 new Archive::Entry(this, QStringLiteral("aDir/b.txt")), 0366 new Archive::Entry(this, QStringLiteral("c.txt")), 0367 }) 0368 << QVector<Archive::Entry *>{new Archive::Entry(this, QStringLiteral("c.txt"))} 0369 << new Archive::Entry(this, QStringLiteral("aDir/")); 0370 0371 // Error test: if we add an already existent entry, the archive must not change. 0372 QTest::newRow("archive001.json") << QFINDTESTDATA("data/archive001.json") 0373 << (QVector<Archive::Entry *>{ 0374 new Archive::Entry(this, QStringLiteral("a.txt")), 0375 new Archive::Entry(this, QStringLiteral("aDir/")), 0376 new Archive::Entry(this, QStringLiteral("aDir/b.txt")), 0377 new Archive::Entry(this, QStringLiteral("c.txt")), 0378 }) 0379 << QVector<Archive::Entry *>{new Archive::Entry(this, QStringLiteral("c.txt"))} << new Archive::Entry(this); 0380 } 0381 0382 void JobsTest::testAddEntries() 0383 { 0384 QFETCH(QString, jsonArchive); 0385 JSONArchiveInterface *iface = createArchiveInterface(jsonArchive); 0386 QVERIFY(iface); 0387 0388 QFETCH(QVector<Archive::Entry *>, originalEntries); 0389 QStringList originalFullPaths = QStringList(); 0390 for (const Archive::Entry *entry : std::as_const(originalEntries)) { 0391 originalFullPaths.append(entry->fullPath()); 0392 } 0393 auto currentEntries = listEntries(iface); 0394 QCOMPARE(currentEntries.size(), originalEntries.size()); 0395 0396 QFETCH(QVector<Archive::Entry *>, entriesToAdd); 0397 QFETCH(Archive::Entry *, destinationEntry); 0398 AddJob *addJob = new AddJob(entriesToAdd, destinationEntry, CompressionOptions(), iface); 0399 startAndWaitForResult(addJob); 0400 0401 QStringList expectedAddedFullPaths = QStringList(); 0402 const QString destinationPath = destinationEntry->fullPath(); 0403 int expectedEntriesCount = originalEntries.size(); 0404 for (const Archive::Entry *entry : std::as_const(entriesToAdd)) { 0405 const QString fullPath = destinationPath + entry->fullPath(); 0406 if (!originalFullPaths.contains(fullPath)) { 0407 expectedEntriesCount++; 0408 expectedAddedFullPaths << destinationPath + entry->fullPath(); 0409 } 0410 } 0411 0412 currentEntries = listEntries(iface); 0413 QCOMPARE(currentEntries.size(), expectedEntriesCount); 0414 0415 QStringList currentFullPaths = QStringList(); 0416 for (const Archive::Entry *entry : std::as_const(currentEntries)) { 0417 currentFullPaths << entry->fullPath(); 0418 } 0419 0420 for (const QString &fullPath : std::as_const(expectedAddedFullPaths)) { 0421 QVERIFY(currentFullPaths.contains(fullPath)); 0422 } 0423 0424 iface->deleteLater(); 0425 } 0426 0427 #include "jobstest.moc"