Page MenuHomePhorge

D210.1750570570.diff
No OneTemporary

Size
17 KB
Referenced Files
None
Subscribers
None

D210.1750570570.diff

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

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)

Event Timeline