Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85804376
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
41 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Oct 10, 4:47 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1785038
Default Alt Text
(41 KB)
Attached To
Mode
rK kazv
Attached
Detach File
Event Timeline
Log In to Comment