Page MenuHomePhorge

No OneTemporary

Size
41 KB
Referenced Files
None
Subscribers
None
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8c4d8c6..8b8f69e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,91 +1,91 @@
cmake_minimum_required(VERSION 3.16)
project(kazv)
include(FeatureSummary)
set(kazv_VERSION_MAJOR 0)
set(kazv_VERSION_MINOR 6)
set(kazv_VERSION_PATCH 0)
set(kazv_VERSION_STRING ${kazv_VERSION_MAJOR}.${kazv_VERSION_MINOR}.${kazv_VERSION_PATCH})
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
set(CMAKE_CXX_STANDARD 17)
if(NOT kazv_KF_QT_MAJOR_VERSION)
set(kazv_KF_QT_MAJOR_VERSION 6)
endif()
message(STATUS "KF/Qt major version: ${kazv_KF_QT_MAJOR_VERSION}")
if(NOT kazv_KF_QT_MAJOR_VERSION EQUAL 6)
message(FATAL_ERROR "KF/Qt major version must be 6")
endif()
set(QT_MAJOR_VERSION ${kazv_KF_QT_MAJOR_VERSION})
set(KF_MAJOR_VERSION ${kazv_KF_QT_MAJOR_VERSION})
set(QT_MIN_VERSION 6.5.0)
set(KF_MIN_VERSION 6.0.0)
option(kazv_LINK_BREEZE_ICONS "Link to Breeze icons library" OFF)
set(kazv_LINK_BREEZE_ICONS_MACRO 0)
if(kazv_LINK_BREEZE_ICONS)
set(kazv_LINK_BREEZE_ICONS_MACRO 1)
endif()
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
set(KDE_SKIP_NULLPTR_WARNINGS_SETTINGS ON)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMSetupVersion)
include(ECMGenerateHeaders)
include(ECMPoQmTools)
include(ECMAddTests)
include(ECMQtDeclareLoggingCategory)
include(ECMDeprecationSettings)
include(ECMQmlModule)
ecm_set_disabled_deprecation_versions(
QT 5.15.14
KF 5.116.0
KCOREADDONS 5.116.0
)
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)
if(kazv_LINK_BREEZE_ICONS)
list(APPEND kazv_KF_EXTRA_MODULES BreezeIcons)
endif()
find_package(KF${KF_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS Kirigami Config KIO Notifications CoreAddons ${kazv_KF_EXTRA_MODULES})
find_package(KF6KirigamiAddons)
find_package(Threads REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(libkazv REQUIRED)
find_package(cmark)
set(CMARK_TARGET_NAME cmark::cmark)
if(NOT cmark_FOUND)
# CMark 0.29.0 on Ubuntu 20.04 has cmark.cmake instead of cmark-config.cmake
# But that is not usable, so we fall back to pkgconfig
find_package(PkgConfig REQUIRED)
pkg_search_module(cmark libcmark IMPORTED_TARGET GLOBAL)
set(CMARK_TARGET_NAME cmark)
endif()
kde_enable_exceptions()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -foperator-names -DQT_NO_EMIT")
add_subdirectory(src)
install(PROGRAMS moe.kazv.mxc.kazv.desktop DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES moe.kazv.mxc.kazv.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
install(FILES kazv.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
install(FILES icons/kazvlogo-256.png DESTINATION ${KDE_INSTALL_ICONDIR}/hicolor/256x256/apps RENAME moe.kazv.mxc.kazv.png)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/packaging/GNU-Linux/appimage/build.sh b/packaging/GNU-Linux/appimage/build.sh
index 342e27b..df116d6 100755
--- a/packaging/GNU-Linux/appimage/build.sh
+++ b/packaging/GNU-Linux/appimage/build.sh
@@ -1,112 +1,114 @@
#!/bin/bash
set -x
set -e
export thisDir="$(readlink -f "$(dirname "$(realpath "$0")")")"
# Collect deps
wget https://raw.githubusercontent.com/KDE/docker-neon/Neon/stable/public.key && \
apt-key add public.key && \
rm public.key
DEP_PACKAGES=(
extra-cmake-modules
wget
weston
dbus-x11
libcmark-dev
)
if [[ $KF_VER == 5 ]]; then
echo "Building on KF5 is no longer supported."
exit 1
else
DEP_PACKAGES+=(
qt6-base-dev
qt6-declarative-dev
qt6-multimedia-dev
qt6-imageformats
qml6-module-qtmultimedia
libqt6concurrent6
libqt6gui6
libqt6svg6-dev
libqt6quickcontrols2-6
qml6-module-qtquick-controls
qml6-module-qtquick-layouts
qml6-module-qt-labs-qmlmodels
kf6-kirigami2-dev
libkf6config-dev
qt6-gtk-platformtheme
plasma-integration
libkf6kio-dev
qml6-module-qttest
qml6-module-qt-labs-platform
libkf6notifications-dev
qml6-module-qtquick-dialogs
qml6-module-org-kde-notification
kf6-kirigami-addons-dev
kf6-kcoreaddons-dev
qml6-module-org-kde-qqc2desktopstyle
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/
fi
apt-get update && \
apt-get -y upgrade && \
DEBIAN_FRONTEND="noninteractive" \
apt-get -y install aptitude && \
aptitude -y install "${DEP_PACKAGES[@]}"
export BUILD_TYPE="$1"
if [[ ! "$BUILD_TYPE" ]]; then
export BUILD_TYPE=Debug
fi
useradd -U builder
mkdir build
chown -R builder:builder ccache
chown builder:builder build
cd build
export JOBS=${JOBS:-$(( $(nproc) / 2))} DEPS_INSTALL_DIR=/opt/libkazv-deps LIBKAZV_INSTALL_DIR=/opt/libkazv KAZV_INSTALL_DIR=/usr
export LD_LIBRARY_PATH="$LIBKAZV_INSTALL_DIR/lib:$LIBKAZV_INSTALL_DIR/lib/x86_64-linux-gnu:$DEPS_INSTALL_DIR/lib:$DEPS_INSTALL_DIR/lib/x86_64-linux-gnu"
mkdir -pv /run/user/test
chown builder:builder /run/user/test
su builder -c "env PATH='$PATH' '$thisDir'/build-unprivileged.sh" || exit 1
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
chmod +x linuxdeploy*.AppImage
export QML_SOURCES_PATHS=../src
./linuxdeploy-plugin-qt-x86_64.AppImage --appimage-extract
./linuxdeploy-x86_64.AppImage --appimage-extract
# https://github.com/linuxdeploy/linuxdeploy/issues/154
mv -v ./linuxdeploy-plugin-qt-x86_64.AppImage ./linuxdeploy-notplugin-qt-x86_64.AppImage
# Enable the appimage to use Plasma themes, linuxdeploy-plugin-qt will only copy the gtk3 plugin -((
mkdir -pv AppDir/usr/plugins/platformthemes/
cp /usr/lib/x86_64-linux-gnu/qt6/plugins/platformthemes/KDEPlasmaPlatformTheme6.so AppDir/usr/plugins/platformthemes/ || true
mkdir -pv AppDir/usr/qml/org/kde
cp -rv /usr/lib/x86_64-linux-gnu/qt6/qml/org/kde/desktop AppDir/usr/qml/org/kde
cp -rv /usr/lib/x86_64-linux-gnu/qt6/qml/org/kde/qqc2desktopstyle AppDir/usr/qml/org/kde
mkdir -pv AppDir/usr/plugins/kf6/kirigami/platform
cp -v /usr/lib/x86_64-linux-gnu/qt6/plugins/kf6/kirigami/platform/org.kde.desktop.so AppDir/usr/plugins/kf6/kirigami/platform/
# Workaround "cannot mix incompatible qt versions" problem
mkdir -pv AppDir/apprun-hooks/
cp -v "$thisDir"/hooks/*.sh AppDir/apprun-hooks/
./squashfs-root/AppRun --appdir AppDir -i ../icons/kazvlogo-256.png --plugin qt --output appimage # -l /lib/x86_64-linux-gnu/libstdc++.so.6
cp kazv*.AppImage ../kazv-"$BUILD_TYPE".AppImage
diff --git a/src/kazv-file.cpp b/src/kazv-file.cpp
index 9d97128..59a639c 100644
--- a/src/kazv-file.cpp
+++ b/src/kazv-file.cpp
@@ -1,124 +1,141 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2024 nannanko <nannanko@kazv.moe>
* SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include "kazv-file.hpp"
#include <QCryptographicHash>
+#include <QThread>
#include <optional>
struct KazvFilePrivate
{
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)
: QFile(name, parent)
, m_d(new KazvFilePrivate)
{
if (aes.has_value()) {
m_d->aes = Kazv::AES256CTRDesc{aes.value().key(), aes.value().iv()};
}
}
KazvFile::KazvFile(std::optional<Kazv::AES256CTRDesc> aes, QObject *parent)
: QFile(parent)
, m_d(new KazvFilePrivate)
{
if (aes.has_value()) {
m_d->aes = Kazv::AES256CTRDesc{aes.value().key(), aes.value().iv()};
}
}
-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()};
}
}
KazvFile::KazvFile(std::optional<Kazv::AES256CTRDesc> aes)
: QFile()
, m_d(new KazvFilePrivate)
{
if (aes.has_value()) {
m_d->aes = Kazv::AES256CTRDesc{aes.value().key(), aes.value().iv()};
}
}
KazvFile::~KazvFile() = default;
QByteArray KazvFile::hash() const
{
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;
std::copy_n(encrypted.data(), readLen, data);
m_d->hash.addData(data, readLen);
}
return readLen;
}
struct KazvSaveFilePrivate {
std::optional<Kazv::AES256CTRDesc> aes;
QCryptographicHash hash{QCryptographicHash::Sha256};
};
KazvSaveFile::KazvSaveFile(const QString &name, std::optional<Kazv::AES256CTRDesc> aes, QObject *parent)
: QSaveFile(name, parent)
, m_d(new KazvSaveFilePrivate)
{
if (aes.has_value()) {
m_d->aes = Kazv::AES256CTRDesc{aes.value().key(), aes.value().iv()};
}
}
KazvSaveFile::KazvSaveFile(std::optional<Kazv::AES256CTRDesc> aes, QObject *parent)
: QSaveFile(parent)
, m_d(new KazvSaveFilePrivate)
{
if (aes.has_value()) {
m_d->aes = Kazv::AES256CTRDesc{aes.value().key(), aes.value().iv()};
}
}
KazvSaveFile::KazvSaveFile(const QString &name, std::optional<Kazv::AES256CTRDesc> aes)
: QSaveFile(name)
, m_d(new KazvSaveFilePrivate)
{
if (aes.has_value()) {
m_d->aes = Kazv::AES256CTRDesc{aes.value().key(), aes.value().iv()};
}
}
KazvSaveFile::~KazvSaveFile() = default;
QByteArray KazvSaveFile::hash() const
{
return m_d->hash.result().toBase64(QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals);
}
qint64 KazvSaveFile::writeData(const char *data, qint64 len)
{
if (m_d->aes.has_value() && len > 0) {
m_d->hash.addData(data, len);
auto result = m_d->aes.value().process(std::string(data, len));
m_d->aes = result.first;
return QSaveFile::writeData(result.second.data(), len);
}
return QSaveFile::writeData(data, len);
}
diff --git a/src/kazv-file.hpp b/src/kazv-file.hpp
index 4462b2c..f631a05 100644
--- a/src/kazv-file.hpp
+++ b/src/kazv-file.hpp
@@ -1,58 +1,61 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2022-2023 nannanko <nannanko@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <kazv-defs.hpp>
#include <aes-256-ctr.hpp>
#include <QObject>
#include <QFile>
#include <QString>
#include <QByteArray>
#include <QSaveFile>
#include <memory>
#include <optional>
struct KazvFilePrivate;
class KazvFile : public QFile
{
Q_OBJECT
std::unique_ptr<KazvFilePrivate> m_d;
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;
};
struct KazvSaveFilePrivate;
class KazvSaveFile : public QSaveFile {
Q_OBJECT
std::unique_ptr<KazvSaveFilePrivate> m_d;
public:
KazvSaveFile(const QString &name, std::optional<Kazv::AES256CTRDesc> aes, QObject *parent);
KazvSaveFile(std::optional<Kazv::AES256CTRDesc> aes, QObject *parent);
KazvSaveFile(const QString &name, std::optional<Kazv::AES256CTRDesc> aes);
~KazvSaveFile();
QByteArray hash() const;
protected:
qint64 writeData(const char *data, qint64 len) override;
};
diff --git a/src/kazv-io-job.cpp b/src/kazv-io-job.cpp
index 0d75e31..fb2ec98 100644
--- a/src/kazv-io-job.cpp
+++ b/src/kazv-io-job.cpp
@@ -1,357 +1,365 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2022-2023 nannanko <nannanko@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include "kazv-io-job.hpp"
#include "matrix-room.hpp"
#include "kazv-log.hpp"
#include <QObject>
#include <QPointer>
#include <QSharedPointer>
#include <QSaveFile>
#include <QFile>
#include <QString>
#include <QMimeDatabase>
#include <QCryptographicHash>
#include <QLoggingCategory>
#include <KJob>
#include <KIO/TransferJob>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
using namespace Qt::Literals::StringLiterals;
struct KazvIOBaseJobPrivate
{
QPointer<KIO::TransferJob> job;
std::optional<KazvIOBaseJob::ErrorCode> result = std::nullopt;
std::optional<Kazv::AES256CTRDesc> aes = std::nullopt;
};
KazvIOBaseJob::KazvIOBaseJob(std::optional<Kazv::AES256CTRDesc> aes, QObject *parent)
: QObject(parent)
, m_d(new KazvIOBaseJobPrivate)
{
/**
* For unknown reasons, if a new AES256CTRDesc is not constructed here,
* it will cause the program to crash.
*/
if (aes != std::nullopt) {
m_d->aes = Kazv::AES256CTRDesc{aes.value().key(), aes.value().iv()};
}
// m_d->aes = aes;
connect(this, &KazvIOBaseJob::jobChanged, this, &KazvIOBaseJob::connectJob);
}
KazvIOBaseJob::~KazvIOBaseJob() = default;
float KazvIOBaseJob::progress() {
if (m_d->job.isNull()) {
return 0;
}
return static_cast<float>(m_d->job->percent()) / 100;
}
void KazvIOBaseJob::suspend()
{
if (m_d->job.isNull()) {
emitResult(KazvError);
return;
}
m_d->job->suspend();
}
void KazvIOBaseJob::resume()
{
if (m_d->job.isNull()) {
emitResult(KazvError);
return;
}
m_d->job->resume();
}
void KazvIOBaseJob::cancel()
{
if (m_d->job.isNull()) {
emitResult(KazvError);
return;
}
emitResult(UserCancel);
m_d->job->kill();
}
bool KazvIOBaseJob::isSuspended()
{
if (m_d->job.isNull()) {
return false;
}
return m_d->job->isSuspended();
}
bool KazvIOBaseJob::isResulted()
{
return m_d->result.has_value();
}
KazvIOBaseJob::ErrorCode KazvIOBaseJob::error()
{
if (m_d->result.has_value()) {
return m_d->result.value();
}
// Shouldn't call this function before result are emited
return NoError;
}
void KazvIOBaseJob::connectJob()
{
connect(m_d->job, &KJob::result, this, [this](KJob *job) {
if (job->error()) {
emitResult(KIOError);
}
});
connect(m_d->job, &KJob::percentChanged, this,
[this](KJob * /* job */, unsigned long /* percent */) { Q_EMIT progressChanged(); });
}
QPointer<KIO::TransferJob> KazvIOBaseJob::job()
{
return m_d->job;
}
void KazvIOBaseJob::setJob(QPointer<KIO::TransferJob> job)
{
m_d->job = job;
Q_EMIT jobChanged();
}
void KazvIOBaseJob::emitResult(ErrorCode ec, QString data)
{
if (m_d->result.has_value()) {
return;
}
m_d->result = ec;
Q_EMIT result(m_d->result.value(), data);
}
std::optional<Kazv::AES256CTRDesc> KazvIOBaseJob::aes()
{
return m_d->aes;
}
void KazvIOBaseJob::setAes(Kazv::AES256CTRDesc aes)
{
m_d->aes = aes;
}
struct KazvIODownloadJobPrivate
{
QSharedPointer<KazvSaveFile> file;
QString hash;
};
KazvIODownloadJob::KazvIODownloadJob(const QString &hash, std::optional<Kazv::AES256CTRDesc> aes, QObject *parent)
: KazvIOBaseJob(aes, parent)
, m_d(new KazvIODownloadJobPrivate)
{
m_d->hash = hash;
}
KazvIODownloadJob::KazvIODownloadJob(const QString &fileName, const QUrl &serverUrl, bool showProgressBar,
const QString &hash, std::optional<Kazv::AES256CTRDesc> aes, QObject *parent)
: KazvIOBaseJob(aes, parent)
, m_d(new KazvIODownloadJobPrivate)
{
if (setFile(fileName)) {
auto flag = showProgressBar ? KIO::DefaultFlags : KIO::HideProgressInfo;
setJob(KIO::get(serverUrl, KIO::NoReload, flag));
}
m_d->hash = hash;
}
KazvIODownloadJob::~KazvIODownloadJob() = default;
QString KazvIODownloadJob::fileName() {
if (m_d->file.isNull()) {
return QStringLiteral("");
}
return m_d->file->fileName();
}
void KazvIODownloadJob::connectJob()
{
KazvIOBaseJob::connectJob();
connect(this->job(), &KIO::TransferJob::data, this, &KazvIODownloadJob::writeFile);
connect(this->job(), &KJob::result, this, &KazvIODownloadJob::closeFile);
}
bool KazvIODownloadJob::setFile(QString fileName)
{
m_d->file.reset(new KazvSaveFile(fileName, this->aes()));
if (m_d->file->open(QIODevice::WriteOnly)) {
return true;
}
emitResult(KazvIOBaseJob::OpenFileError);
return false;
}
void KazvIODownloadJob::writeFile(KJob *job, const QByteArray &data)
{
if (m_d->file.isNull() || !m_d->file->isOpen()) {
emitResult(KazvError);
job->kill();
return;
}
auto len = m_d->file->write(data);
if (len == -1) {
KazvIOBaseJob::emitResult(WriteFileError);
job->kill();
}
}
void KazvIODownloadJob::closeFile(KJob *job)
{
if (m_d->file.isNull()) {
emitResult(KazvError);
return;
}
if (job->error()) {
m_d->file->cancelWriting();
return;
}
if (!m_d->hash.isEmpty() && QString::fromUtf8(m_d->file->hash()) != m_d->hash) {
emitResult(HashError);
return;
}
m_d->file->commit();
emitResult(NoError);
}
struct KazvIOUploadJobPrivate
{
QSharedPointer<KazvFile> file;
QString response;
QSharedPointer<MatrixRoom> room;
QString mimeType;
QString mxcUri;
QString relType;
QString relatedTo;
+ bool testing{false};
};
KazvIOUploadJob::KazvIOUploadJob(std::optional<Kazv::AES256CTRDesc> aes,
const QString &relType, const QString &relatedTo, QObject *parent)
: KazvIOBaseJob(aes, parent)
, m_d(new KazvIOUploadJobPrivate)
{
m_d->relType = relType;
m_d->relatedTo = relatedTo;
}
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()));
auto jobFlag = showProgressBar ? KIO::DefaultFlags : KIO::HideProgressInfo;
auto job = KIO::http_post(
QUrl(QString::fromStdString(kazvUploadJob.url())),
m_d->file.data(), jobFlag);
job->addMetaData(u"customHTTPHeader"_s, QStringLiteral("Authorization: ")
.append(QString::fromStdString(kazvUploadJob.requestHeader()->at("Authorization"))));
job->addMetaData(u"PropagateHttpHeader"_s, u"true"_s);
setJob(job);
if (roomList) {
m_d->room.reset(roomList->room(roomId));
}
}
}
KazvIOUploadJob::~KazvIOUploadJob() = default;
QString KazvIOUploadJob::fileName()
{
if (m_d->file.isNull()) {
return QStringLiteral("");
}
return m_d->file->fileName();
}
void KazvIOUploadJob::connectJob()
{
KazvIOBaseJob::connectJob();
connect(this->job(), &KIO::TransferJob::data, this,
[this](KJob * /* job */, const QByteArray &data) { m_d->response.append(QString::fromUtf8(data)); });
connect(this->job(), &KJob::result, this, &KazvIOUploadJob::handleResult);
connect(this->job(), &KIO::TransferJob::mimeTypeFound, this,
[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()) {
emitResult(KazvError);
return;
}
m_d->file->close();
if (job->error()) {
return;
}
nlohmann::json j;
try {
j = nlohmann::json::parse(m_d->response.toStdString());
} catch(...) {
emitResult(ResponseError);
qCWarning(kazvLog) << "Get a invaild response from server.";
return;
}
QString mxcUri{};
if (j.contains("content_uri") && j["content_uri"].is_string()) {
mxcUri = QString::fromStdString(j["content_uri"].template get<std::string>());
} else {
emitResult(ResponseError);
qCWarning(kazvLog) << "There is no \"content_uri\" field in response.";
return;
}
auto shouldSendMessage = !!m_d->room;
if (shouldSendMessage) {
auto aes = this->aes();
if (aes.has_value()) {
m_d->room->sendEncryptedFileMessage(QUrl::fromLocalFile(m_d->file->fileName()).fileName(),
QMimeDatabase().mimeTypeForFile(m_d->file.data()->fileName()).name(),
m_d->file->size(), mxcUri,
QString::fromStdString(aes.value().key()), QString::fromStdString(aes.value().iv()),
m_d->file->hash(), m_d->relType, m_d->relatedTo);
} else {
m_d->room->sendMediaFileMessage(QUrl::fromLocalFile(m_d->file->fileName()).fileName(),
QMimeDatabase().mimeTypeForData(m_d->file.data()).name(),
m_d->file->size(), mxcUri, m_d->relType, m_d->relatedTo);
}
}
KazvIOBaseJob::emitResult(NoError, mxcUri);
}
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;
}
emitResult(KazvIOBaseJob::OpenFileError);
return false;
}
diff --git a/src/kazv-io-job.hpp b/src/kazv-io-job.hpp
index a9a374e..8563f10 100644
--- a/src/kazv-io-job.hpp
+++ b/src/kazv-io-job.hpp
@@ -1,165 +1,169 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2022-2023 nannanko <nannanko@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <kazv-defs.hpp>
#include "matrix-room.hpp"
#include "matrix-room-list.hpp"
#include "kazv-file.hpp"
#include <aes-256-ctr.hpp>
#include <QObject>
#include <QPointer>
#include <QString>
#include <QUrl>
#include <KIO/TransferJob>
#include <memory>
#include <optional>
struct KazvIOBaseJobPrivate;
class KazvIOBaseJob : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
Q_PROPERTY(float progress READ progress NOTIFY progressChanged)
std::unique_ptr<KazvIOBaseJobPrivate> m_d;
public:
enum ErrorCode {
/**
* No Error or the signal result has not been emitted yet.
*/
NoError = 0,
/**
* The user actively canceled the job.
*/
UserCancel,
/**
* Open File Error.
*/
OpenFileError,
/**
* Write File Error
*/
WriteFileError,
/**
* Errors caused by KIO, such network error.
*/
KIOError,
/**
* Error caused by hash error
*/
HashError,
/**
* Error caused by invalid response.
* KIO reported no error, but got an invalid JSON response.
*/
ResponseError,
/**
* Errors caused by bad code, means the bug is somewhere.
*/
KazvError
};
Q_ENUM(ErrorCode)
KazvIOBaseJob(std::optional<Kazv::AES256CTRDesc> aes = std::nullopt, QObject *parent = 0);
~KazvIOBaseJob();
float progress();
Q_INVOKABLE void suspend();
Q_INVOKABLE void resume();
Q_INVOKABLE void cancel();
Q_INVOKABLE bool isSuspended();
Q_INVOKABLE bool isResulted();
/**
* Only use it after emitted the result signal
*/
Q_INVOKABLE ErrorCode error();
Q_SIGNALS:
void jobChanged();
void progressChanged();
/**
* @param data the mxcUri if this is an upload job.
*/
void result(ErrorCode ec, QString data);
protected Q_SLOTS:
virtual void connectJob();
protected:
QPointer<KIO::TransferJob> job();
void setJob(QPointer<KIO::TransferJob> job);
std::optional<Kazv::AES256CTRDesc> aes();
void setAes(Kazv::AES256CTRDesc);
void emitResult(ErrorCode ec, QString data = QString());
};
struct KazvIODownloadJobPrivate;
class KazvIODownloadJob : public KazvIOBaseJob
{
Q_OBJECT
std::unique_ptr<KazvIODownloadJobPrivate> m_d;
bool setFile(QString fileName);
public:
KazvIODownloadJob(const QString &hash, std::optional<Kazv::AES256CTRDesc> aes = std::nullopt, QObject *parent = 0);
KazvIODownloadJob(const QString &fileName, const QUrl &serverUrl, const bool showProgressBar, const QString &hash,
std::optional<Kazv::AES256CTRDesc> aes = std::nullopt, QObject *parent = 0);
~KazvIODownloadJob();
Q_INVOKABLE QString fileName();
private Q_SLOTS:
void writeFile(KJob *job, const QByteArray &data);
void closeFile(KJob *job);
protected Q_SLOTS:
void connectJob() override;
};
struct KazvIOUploadJobPrivate;
class KazvIOUploadJob : public KazvIOBaseJob
{
Q_OBJECT
std::unique_ptr<KazvIOUploadJobPrivate> m_d;
bool setFile(const QString fileName);
public:
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);
protected Q_SLOTS:
void connectJob() override;
};
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 279aa6b..040f492 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,50 +1,55 @@
set(KAZV_TEST_TEMP_DIR ${CMAKE_CURRENT_BINARY_DIR}/test-temp)
configure_file(test-temp.hpp.in test-temp.hpp)
set(kazvtestlib_SRCS
test-model.cpp
)
add_library(kazvtestlib STATIC ${kazvtestlib_SRCS})
target_include_directories(kazvtestlib PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(kazvtestlib PUBLIC kazvprivlib libkazv::kazvtestfixtures)
add_executable(matrix-sdk-session-loader matrix-sdk-session-loader.cpp matrix-sdk-sessions-test.cpp)
target_compile_definitions(matrix-sdk-session-loader PRIVATE MATRIX_SDK_SESSIONS_TEST_NO_MAIN)
target_link_libraries(matrix-sdk-session-loader Qt${QT_MAJOR_VERSION}::Test kazvtestlib)
ecm_add_tests(
qt-job-handler-test.cpp
qt-promise-handler-test.cpp
qt-json-test.cpp
device-mgmt-test.cpp
matrix-sdk-test.cpp
matrix-sdk-sessions-test.cpp
matrix-promise-test.cpp
matrix-room-timeline-test.cpp
matrix-room-member-list-model-test.cpp
matrix-room-list-test.cpp
kazv-markdown-test.cpp
matrix-sticker-pack-test.cpp
matrix-link-test.cpp
matrix-user-given-attrs-map-test.cpp
kazv-abstract-list-model-test.cpp
matrix-event-list-test.cpp
matrix-event-test.cpp
kazv-file-test.cpp
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
LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::QuickTest kazvtestlib
)
if(KAZV_BUILD_BENCHMARKS)
ecm_add_test(
matrix-room-timeline-benchmark-test.cpp
LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::Test kazvtestlib
)
endif()
set_tests_properties(quicktest PROPERTIES WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
diff --git a/src/tests/kazv-io-job-test.cpp b/src/tests/kazv-io-job-test.cpp
new file mode 100644
index 0000000..ca2a8fc
--- /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/x-diff
Expires
Sat, Oct 10, 4:47 AM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1785038
Default Alt Text
(41 KB)

Event Timeline