Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2575463
D210.1750570570.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
D210.1750570570.diff
View Options
diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,7 +52,7 @@
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} REQUIRED COMPONENTS
Core Gui Qml QuickControls2 Svg Concurrent Widgets
- Multimedia Test Network QuickTest
+ Multimedia Test Network QuickTest HttpServer
)
qt6_policy(SET QTP0001 NEW)
set(kazv_KF_EXTRA_MODULES)
diff --git a/packaging/GNU-Linux/appimage/build.sh b/packaging/GNU-Linux/appimage/build.sh
--- a/packaging/GNU-Linux/appimage/build.sh
+++ b/packaging/GNU-Linux/appimage/build.sh
@@ -51,6 +51,8 @@
kf6-qqc2-desktop-style-dev
kf6-breeze-icon-theme-dev
cmark
+ qt6-httpserver-dev
+ qt6-websockets-dev
)
export QMAKE=qmake6
cp -v packaging/GNU-Linux/appimage/kde-neon-jammy.list /etc/apt/sources.list.d/
diff --git a/src/kazv-file.hpp b/src/kazv-file.hpp
--- a/src/kazv-file.hpp
+++ b/src/kazv-file.hpp
@@ -28,12 +28,15 @@
public:
KazvFile(const QString &name, std::optional<Kazv::AES256CTRDesc> aes, QObject *parent);
KazvFile(std::optional<Kazv::AES256CTRDesc> aes, QObject *parent);
- KazvFile(const QString &name, std::optional<Kazv::AES256CTRDesc> aes);
+ // Parameter testing is ONLY for unit tests, otherwise MUST be set to false
+ KazvFile(const QString &name, std::optional<Kazv::AES256CTRDesc> aes, const bool testing = false);
KazvFile(std::optional<Kazv::AES256CTRDesc> aes);
~KazvFile();
QByteArray hash() const;
+ void testResume(); // Just for unit test
+
protected:
qint64 readData(char *data, qint64 len) override;
};
diff --git a/src/kazv-file.cpp b/src/kazv-file.cpp
--- a/src/kazv-file.cpp
+++ b/src/kazv-file.cpp
@@ -9,6 +9,7 @@
#include "kazv-file.hpp"
#include <QCryptographicHash>
+#include <QThread>
#include <optional>
@@ -16,6 +17,8 @@
{
std::optional<Kazv::AES256CTRDesc> aes;
QCryptographicHash hash{QCryptographicHash::Sha256};
+ bool testing{false};
+ bool testReadyResume{false}; // Just fot unit test
};
KazvFile::KazvFile(const QString &name, std::optional<Kazv::AES256CTRDesc> aes, QObject *parent)
@@ -36,10 +39,11 @@
}
}
-KazvFile::KazvFile(const QString &name, std::optional<Kazv::AES256CTRDesc> aes)
+KazvFile::KazvFile(const QString &name, std::optional<Kazv::AES256CTRDesc> aes, const bool testing)
: QFile(name)
, m_d(new KazvFilePrivate)
{
+ m_d->testing = testing;
if (aes.has_value()) {
m_d->aes = Kazv::AES256CTRDesc{aes.value().key(), aes.value().iv()};
}
@@ -61,9 +65,22 @@
return m_d->hash.result().toBase64(QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals);
}
+void KazvFile::testResume()
+{
+ m_d->testReadyResume = true;
+}
+
qint64 KazvFile::readData(char *data, qint64 len)
{
- qint64 readLen = QFile::readData(data, len);
+ qint64 readLen;
+ if (m_d->testing && !m_d->testReadyResume) {
+ using namespace std::literals::chrono_literals;
+ QThread::sleep(10ms);
+ readLen = QFile::readData(data, 1);
+ } else {
+ readLen = QFile::readData(data, len);
+ }
+
if (m_d->aes.has_value() && readLen > 0) {
auto [next, encrypted] = m_d->aes.value().process(std::string(data, readLen));
m_d->aes = next;
diff --git a/src/kazv-io-job.hpp b/src/kazv-io-job.hpp
--- a/src/kazv-io-job.hpp
+++ b/src/kazv-io-job.hpp
@@ -147,16 +147,20 @@
KazvIOUploadJob(std::optional<Kazv::AES256CTRDesc> aes = std::nullopt,
const QString &relType = QStringLiteral(""),
const QString &relatedTo = QStringLiteral(""), QObject *parent = 0);
+ // Parameter testing is ONLY for unit tests, otherwise MUST be set to false
KazvIOUploadJob(const QString fileName, const QUrl serverUrl,
const bool showProgressBar, MatrixRoomList *roomList,
const QString &roomId, const QString token,
std::optional<Kazv::AES256CTRDesc> aes = std::nullopt,
const QString &relType = QStringLiteral(""),
- const QString &relatedTo = QStringLiteral(""), QObject *parent = 0);
+ const QString &relatedTo = QStringLiteral(""),
+ const bool testing = false, QObject *parent = 0);
~KazvIOUploadJob();
Q_INVOKABLE QString fileName();
+ void testResume(); // Just for unit test
+
private Q_SLOTS:
void handleResult(KJob *job);
diff --git a/src/kazv-io-job.cpp b/src/kazv-io-job.cpp
--- a/src/kazv-io-job.cpp
+++ b/src/kazv-io-job.cpp
@@ -243,6 +243,7 @@
QString mxcUri;
QString relType;
QString relatedTo;
+ bool testing{false};
};
KazvIOUploadJob::KazvIOUploadJob(std::optional<Kazv::AES256CTRDesc> aes,
@@ -257,12 +258,14 @@
KazvIOUploadJob::KazvIOUploadJob(const QString fileName, const QUrl serverUrl,
const bool showProgressBar, MatrixRoomList *roomList, const QString &roomId,
const QString token, std::optional<Kazv::AES256CTRDesc> aes,
- const QString &relType, const QString &relatedTo, QObject *parent)
+ const QString &relType, const QString &relatedTo,
+ const bool testing, QObject *parent)
: KazvIOBaseJob(aes, parent)
, m_d(new KazvIOUploadJobPrivate)
{
m_d->relType = relType;
m_d->relatedTo = relatedTo;
+ m_d->testing = testing;
if (setFile(fileName)) {
auto kazvUploadJob = Kazv::Api::UploadContentJob(serverUrl.toString().toStdString(),
token.toStdString(), Kazv::FileDesc(std::string()));
@@ -300,6 +303,11 @@
[this](KJob * /* job */, const QString &mimeType) { m_d->mimeType = mimeType; });
}
+void KazvIOUploadJob::testResume()
+{
+ m_d->file->testResume();
+}
+
void KazvIOUploadJob::handleResult(KJob *job)
{
if (m_d->file.isNull()) {
@@ -348,7 +356,7 @@
bool KazvIOUploadJob::setFile(const QString fileName)
{
- m_d->file.reset(new KazvFile(fileName, this->aes()));
+ m_d->file.reset(new KazvFile(fileName, this->aes(), m_d->testing));
if (m_d->file->open(QIODevice::ReadOnly)) {
return true;
}
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -35,6 +35,11 @@
LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::Test kazvtestlib
)
+ecm_add_test(
+ kazv-io-job-test.cpp
+ LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::Test Qt${QT_MAJOR_VERSION}::HttpServer kazvtestlib
+)
+
ecm_add_test(
quick-test.cpp
TEST_NAME quicktest
diff --git a/src/tests/kazv-io-job-test.cpp b/src/tests/kazv-io-job-test.cpp
new file mode 100644
--- /dev/null
+++ b/src/tests/kazv-io-job-test.cpp
@@ -0,0 +1,391 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2025 nannanko <nannanko@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <kazv-defs.hpp>
+#include <kazv-io-job.hpp>
+
+#include <QObject>
+#include <QtTest>
+#include <QHttpServer>
+#include <QTcpServer>
+#include <QThread>
+#include <QTemporaryFile>
+#include <QCryptographicHash>
+#include <QString>
+#include <QHttpServerResponse>
+#include <QHttpServerRequest>
+#include <QSignalSpy>
+#include <QJsonObject>
+#include <QtGlobal>
+
+using namespace Qt::Literals::StringLiterals;
+
+class KazvIOJobTest : public QObject
+{
+ Q_OBJECT
+
+private:
+ QHttpServer httpServer;
+ QTcpServer tcpServer; // Required by QHttpServer
+ QThread serverThread;
+ quint16 port;
+
+ QTemporaryFile downloadFile;
+ QTemporaryFile uploadFile;
+ QCryptographicHash downloadFileHash{QCryptographicHash::Sha256};
+ QString hashStr;
+
+ const QString downloadEndpoint =
+ u"/_matrix/client/v1/media/download/serverName/download"_s;
+ const QString downloadPauseEndpoint =
+ u"/_matrix/client/v1/media/download/serverName/pause"_s;
+ const QString downloadCancelEndpoint =
+ u"/_matrix/client/v1/media/download/serverName/cancel"_s;
+ const QString uploadEndpoint = u"/_matrix/media/v3/upload"_s;
+ const char *downloadFileContent = "download";
+ const char *uploadFileContent = "upload";
+ const char *responseErrorContent = "ResponseError";
+ QString serverUrl;
+
+private Q_SLOTS:
+ void initTestCase();
+ void cleanupTestCase();
+ void testDownload();
+ void testUpload();
+ void testDownloadPause();
+ void testUploadPause();
+ void testDownloadCancel();
+ void testUploadCancel();
+ void testDownloadHashError();
+ void testDownloadFileName();
+ void testUploadFileName();
+ void testDownloadOpenFileError();
+ void testUploadOpenFileError();
+ void testDownloadKIOError();
+ void testUploadKIOError();
+ void testResponseError();
+
+Q_SIGNALS:
+ void readyPause();
+ void readyResume();
+ void readyCancel();
+ void canceled();
+};
+
+void KazvIOJobTest::initTestCase()
+{
+ downloadFile.open();
+ downloadFile.write(downloadFileContent);
+ downloadFile.close();
+ downloadFileHash.addData(&downloadFile);
+ hashStr = QString::fromUtf8(downloadFileHash.result().toBase64(
+ QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals));
+
+ // QHttpServer::route() requires QHttpServerResponder must be passed by universal reference before Qt6.8
+ // https://doc.qt.io/qt-6.5/qhttpserver.html#route
+#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
+ using QHttpServerResponderRef = QHttpServerResponder &&;
+#else
+ using QHttpServerResponderRef = QHttpServerResponder &;
+#endif
+ httpServer.route(downloadEndpoint, [this](QHttpServerResponderRef res) {
+ downloadFile.open();
+ res.write(downloadFile.readAll(), "application/octet-stream"_ba);
+ downloadFile.close();
+ });
+ httpServer.route(downloadPauseEndpoint, [this](QHttpServerResponderRef res) {
+ downloadFile.open();
+ QSignalSpy qs{this, &KazvIOJobTest::readyResume};
+ Q_EMIT readyPause();
+ QVERIFY(qs.wait());
+ res.write(downloadFile.readAll(), "application/octet-stream"_ba);
+ downloadFile.close();
+ });
+ httpServer.route(downloadCancelEndpoint, [this](QHttpServerResponderRef /* res */) {
+ downloadFile.open();
+ QSignalSpy qs{this, &KazvIOJobTest::canceled};
+ Q_EMIT readyCancel();
+ QVERIFY(qs.wait());
+ return;
+ });
+ httpServer.route(uploadEndpoint, [this](const QHttpServerRequest &req) {
+ if (req.body() == uploadFileContent) {
+ uploadFile.open();
+ uploadFile.write(req.body());
+ uploadFile.close();
+ auto resJson = QJsonObject{{u"content_uri"_s, u"mxc://uri"_s}};
+ return QHttpServerResponse{
+ resJson, QHttpServerResponse::StatusCode::Ok};
+ } else if (req.body() == responseErrorContent) {
+ return QHttpServerResponse{QHttpServerResponder::StatusCode::Ok};
+ }
+ return QHttpServerResponse(QHttpServerResponder::StatusCode::Ok);
+ });
+ QVERIFY(tcpServer.listen());
+ httpServer.bind(&tcpServer);
+
+ port = tcpServer.serverPort();
+ serverUrl = u"http://localhost:"_s + QString::number(port);
+ httpServer.moveToThread(&serverThread);
+ serverThread.start();
+}
+
+void KazvIOJobTest::cleanupTestCase()
+{
+ serverThread.quit();
+ serverThread.wait();
+}
+
+void KazvIOJobTest::testDownload()
+{
+
+ // QTemporaryFile cannot be written by QSaveFile, use QTemporaryDir instead.
+ QTemporaryDir dir{};
+ auto fileName = dir.filePath(u"savedFile"_s);
+ auto url = QUrl{serverUrl + downloadEndpoint};
+
+ KazvIODownloadJob job{fileName, url, false, hashStr};
+ QTRY_VERIFY(job.isResulted());
+
+ QCOMPARE(job.error(), KazvIOBaseJob::NoError);
+
+ QFile savedFile{fileName};
+ savedFile.open(QIODevice::ReadOnly);
+ downloadFile.open();
+ QCOMPARE(downloadFile.readAll(), savedFile.readAll());
+ downloadFile.close();
+ savedFile.close();
+}
+
+void KazvIOJobTest::testUpload()
+{
+ QTemporaryFile file;
+ file.open();
+ file.write(uploadFileContent);
+ file.close();
+ auto url = QUrl{serverUrl};
+
+ KazvIOUploadJob job{
+ file.fileName(), url, false, nullptr, u""_s, u"token"_s};
+ QTRY_VERIFY(job.isResulted());
+
+ QCOMPARE(job.error(), KazvIOBaseJob::NoError);
+
+ file.open();
+ uploadFile.open();
+ QCOMPARE(uploadFile.readAll(), file.readAll());
+ uploadFile.close();
+ file.close();
+}
+
+void KazvIOJobTest::testDownloadPause()
+{
+ // QTemporaryFile cannot be written by QSaveFile, use QTemporaryDir instead.
+ QTemporaryDir dir{};
+ auto fileName = dir.filePath(u"savedFile"_s);
+ auto url = QUrl{serverUrl + downloadPauseEndpoint};
+
+ KazvIODownloadJob job{fileName, url, false, hashStr};
+ QSignalSpy qs{this, &KazvIOJobTest::readyPause};
+ QVERIFY(qs.wait());
+ job.suspend();
+ QVERIFY(job.isSuspended());
+ job.resume();
+ Q_EMIT readyResume();
+ QTRY_VERIFY(job.isResulted());
+
+ QCOMPARE(job.error(), KazvIOBaseJob::NoError);
+
+ QFile savedFile{fileName};
+ savedFile.open(QIODevice::ReadOnly);
+ downloadFile.open();
+ QCOMPARE(downloadFile.readAll(), savedFile.readAll());
+ downloadFile.close();
+ savedFile.close();
+}
+
+void KazvIOJobTest::testUploadPause()
+{
+ QTemporaryFile file;
+ file.open();
+ file.write(uploadFileContent);
+ file.close();
+ auto url = QUrl{serverUrl};
+
+ KazvIOUploadJob job{file.fileName(), url, false, nullptr,
+ u""_s, u"token"_s, std::nullopt, u""_s, u""_s, true};
+ job.suspend();
+ QVERIFY(job.isSuspended());
+ job.resume();
+ job.testResume();
+ QTRY_VERIFY(job.isResulted());
+
+ QCOMPARE(job.error(), KazvIOBaseJob::NoError);
+
+ file.open();
+ uploadFile.open();
+ QCOMPARE(uploadFile.readAll(), file.readAll());
+ uploadFile.close();
+ file.close();
+}
+
+void KazvIOJobTest::testDownloadCancel()
+{
+ // QTemporaryFile cannot be written by QSaveFile, use QTemporaryDir instead.
+ QTemporaryDir dir{};
+ auto fileName = dir.filePath(u"savedFile"_s);
+ auto url = QUrl{serverUrl + downloadCancelEndpoint};
+
+ KazvIODownloadJob job{fileName, url, false, hashStr};
+ QSignalSpy qs{this, &KazvIOJobTest::readyCancel};
+ QVERIFY(qs.wait());
+ job.cancel();
+ Q_EMIT canceled();
+ QTRY_VERIFY(job.isResulted());
+
+ QCOMPARE(job.error(), KazvIOBaseJob::UserCancel);
+
+ QFile savedFile{fileName};
+ QVERIFY(!savedFile.exists());
+}
+
+void KazvIOJobTest::testUploadCancel()
+{
+ QTemporaryFile file;
+ file.open();
+ file.write(uploadFileContent);
+ file.close();
+ auto url = QUrl{serverUrl};
+
+ KazvIOUploadJob job{file.fileName(), url, false, nullptr,
+ u""_s, u"token"_s, std::nullopt, u""_s, u""_s, true};
+ job.cancel();
+ QTRY_VERIFY(job.isResulted());
+
+ QCOMPARE(job.error(), KazvIOBaseJob::UserCancel);
+}
+
+void KazvIOJobTest::testDownloadHashError()
+{
+ // QTemporaryFile cannot be written by QSaveFile, use QTemporaryDir instead.
+ QTemporaryDir dir{};
+ auto fileName = dir.filePath(u"savedFile"_s);
+ auto url = QUrl{serverUrl + downloadEndpoint};
+
+ KazvIODownloadJob job{fileName, url, false, u"WrongHash"_s};
+
+ QTRY_VERIFY(job.isResulted());
+
+ QCOMPARE(job.error(), KazvIOBaseJob::HashError);
+
+ QFile savedFile{fileName};
+ QVERIFY(!savedFile.exists());
+}
+
+void KazvIOJobTest::testDownloadFileName()
+{
+ // QTemporaryFile cannot be written by QSaveFile, use QTemporaryDir instead.
+ QTemporaryDir dir{};
+ auto fileName = dir.filePath(u"savedFile"_s);
+ auto url = QUrl{serverUrl + downloadEndpoint};
+
+ KazvIODownloadJob job{fileName, url, false, hashStr};
+ QCOMPARE(job.fileName(), fileName);
+}
+
+void KazvIOJobTest::testUploadFileName()
+{
+ QTemporaryFile file;
+ file.open();
+ file.write(uploadFileContent);
+ file.close();
+ auto url = QUrl{serverUrl};
+
+ KazvIOUploadJob job{
+ file.fileName(), url, false, nullptr, u""_s, u"token"_s};
+ QCOMPARE(job.fileName(), file.fileName());
+}
+
+void KazvIOJobTest::testDownloadOpenFileError()
+{
+ // QTemporaryFile cannot be written by QSaveFile, use QTemporaryDir instead.
+ QTemporaryDir dir{};
+ auto fileName = dir.filePath(u"savedFile"_s);
+ auto url = QUrl{serverUrl + downloadEndpoint};
+
+ QFile savedFile{fileName};
+ savedFile.open(QIODevice::ReadWrite);
+ savedFile.close();
+ // Remove all permissions so that Qt cannot open this file
+ QVERIFY(savedFile.setPermissions({}));
+
+ KazvIODownloadJob job{fileName, url, false, hashStr};
+ QTRY_VERIFY(job.isResulted());
+
+ QCOMPARE(job.error(), KazvIOBaseJob::OpenFileError);
+}
+
+void KazvIOJobTest::testUploadOpenFileError()
+{
+ QTemporaryFile file;
+ file.open();
+ file.write(uploadFileContent);
+ file.close();
+ auto url = QUrl{serverUrl};
+ // Remove all permissions so that Qt cannot open this file
+ QVERIFY(file.setPermissions({}));
+
+ KazvIOUploadJob job{
+ file.fileName(), url, false, nullptr, u""_s, u"token"_s};
+ QTRY_VERIFY(job.isResulted());
+
+ QCOMPARE(job.error(), KazvIOBaseJob::OpenFileError);
+}
+
+void KazvIOJobTest::testDownloadKIOError()
+{
+ // QTemporaryFile cannot be written by QSaveFile, use QTemporaryDir instead.
+ QTemporaryDir dir{};
+ auto fileName = dir.filePath(u"savedFile"_s);
+ auto wrongUrl = QUrl{};
+
+ KazvIODownloadJob job{fileName, wrongUrl, false, hashStr};
+ QTRY_VERIFY(job.isResulted());
+ QCOMPARE(job.error(), KazvIOBaseJob::KIOError);
+}
+
+void KazvIOJobTest::testUploadKIOError()
+{
+ QTemporaryFile file;
+ file.open();
+ file.write(uploadFileContent);
+ file.close();
+ auto wrongUrl = QUrl{};
+
+ KazvIOUploadJob job{
+ file.fileName(), wrongUrl, false, nullptr, u""_s, u"token"_s};
+ QTRY_VERIFY(job.isResulted());
+ QCOMPARE(job.error(), KazvIOBaseJob::KIOError);
+}
+
+void KazvIOJobTest::testResponseError()
+{
+ QTemporaryFile file;
+ file.open();
+ file.write(responseErrorContent);
+ file.close();
+ auto url = QUrl{serverUrl};
+
+ KazvIOUploadJob job{
+ file.fileName(), url, false, nullptr, u""_s, u"token"_s};
+ QTRY_VERIFY(job.isResulted());
+
+ QCOMPARE(job.error(), KazvIOBaseJob::ResponseError);
+}
+
+QTEST_MAIN(KazvIOJobTest)
+
+#include "kazv-io-job-test.moc"
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Jun 21, 10:36 PM (15 h, 55 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
234032
Default Alt Text
D210.1750570570.diff (17 KB)
Attached To
Mode
D210: Add kazv-io-job-test
Attached
Detach File
Event Timeline
Log In to Comment