diff --git a/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt b/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt
index 9c5eb38f..fafd1c25 100644
--- a/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt
+++ b/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt
@@ -186,9 +186,9 @@ enable_testing()
# Tests
#*****************************************************************************************************************************************************
add_executable(bridgepp-test
- Test/Exception/TestBridgeUtils.cpp
- Test/Exception/TestException.cpp
- )
+ Test/TestBridgeUtils.cpp
+ Test/TestException.cpp
+ Test/TestWorker.cpp Test/TestWorker.h)
add_dependencies(bridgepp-test bridgepp)
target_precompile_headers(bridgepp-test PRIVATE Pch.h)
target_link_libraries(bridgepp-test
diff --git a/internal/frontend/bridge-gui/bridgepp/Test/Exception/TestBridgeUtils.cpp b/internal/frontend/bridge-gui/bridgepp/Test/TestBridgeUtils.cpp
similarity index 100%
rename from internal/frontend/bridge-gui/bridgepp/Test/Exception/TestBridgeUtils.cpp
rename to internal/frontend/bridge-gui/bridgepp/Test/TestBridgeUtils.cpp
diff --git a/internal/frontend/bridge-gui/bridgepp/Test/Exception/TestException.cpp b/internal/frontend/bridge-gui/bridgepp/Test/TestException.cpp
similarity index 100%
rename from internal/frontend/bridge-gui/bridgepp/Test/Exception/TestException.cpp
rename to internal/frontend/bridge-gui/bridgepp/Test/TestException.cpp
diff --git a/internal/frontend/bridge-gui/bridgepp/Test/TestWorker.cpp b/internal/frontend/bridge-gui/bridgepp/Test/TestWorker.cpp
new file mode 100644
index 00000000..6f6eb5bb
--- /dev/null
+++ b/internal/frontend/bridge-gui/bridgepp/Test/TestWorker.cpp
@@ -0,0 +1,215 @@
+// Copyright (c) 2023 Proton AG
+//
+// This file is part of Proton Mail Bridge.
+//
+// Proton Mail Bridge is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Proton Mail Bridge is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Proton Mail Bridge. If not, see .
+
+// clazy:excludeall=lambda-in-connect
+
+#include "TestWorker.h"
+#include
+#include
+
+
+using namespace bridgepp;
+
+
+namespace {
+
+
+qint32 dummyArgc = 1; ///< A dummy int value because QCoreApplication constructor requires a reference to it.
+
+
+}
+
+
+//****************************************************************************************************************************************************
+//
+//****************************************************************************************************************************************************
+Workers::Workers()
+ : testing::Test()
+ , app_(dummyArgc, nullptr) {
+}
+
+
+//****************************************************************************************************************************************************
+//
+//****************************************************************************************************************************************************
+void Workers::SetUp() {
+ Test::SetUp();
+
+ EXPECT_NO_THROW(worker_ = new TestWorker);
+
+ QObject::connect(worker_, &TestWorker::started, [&]() { results_.started = true; });
+ QObject::connect(worker_, &TestWorker::finished, [&]() { results_.finished = true; });
+ QObject::connect(worker_, &TestWorker::finished, &loop_, &QEventLoop::quit);
+ QObject::connect(worker_, &TestWorker::error, [&] { results_.error = true; });
+ QObject::connect(worker_, &TestWorker::error, &loop_, &QEventLoop::quit);
+ QObject::connect(worker_, &TestWorker::error, [&] { results_.error = true; });
+ QObject::connect(worker_, &TestWorker::error, &loop_, &QEventLoop::quit);
+ QObject::connect(worker_, &TestWorker::cancelled, [&] { results_.cancelled = true; });
+ QObject::connect(worker_, &TestWorker::cancelled, &loop_, &QEventLoop::quit);
+
+ overseer_ = std::make_unique(worker_, nullptr);
+}
+
+
+//****************************************************************************************************************************************************
+//
+//****************************************************************************************************************************************************
+void Workers::TearDown() {
+ EXPECT_NO_FATAL_FAILURE(overseer_.reset());
+ Test::TearDown();
+}
+
+
+//****************************************************************************************************************************************************
+/// \param[in] lifetimeMs The lifetime of the worker in milliseconds.
+/// \param[in] willSucceed Will the worker succeed (emit finished) or fail (emit error).
+//****************************************************************************************************************************************************
+TestWorker::TestWorker()
+ : Worker(nullptr) {
+}
+
+
+//****************************************************************************************************************************************************
+/// \param[in] lifetimeMs The lifetime of the worker in milliseconds.
+//****************************************************************************************************************************************************
+void TestWorker::setLifetime(qint64 lifetimeMs) {
+ lifetimeMs_ = lifetimeMs;
+}
+
+
+//****************************************************************************************************************************************************
+/// \param[in] willSucceed Will the worker succeed?
+//****************************************************************************************************************************************************
+void TestWorker::setWillSucceed(bool willSucceed) {
+ willSucceed_ = willSucceed;
+}
+
+
+//****************************************************************************************************************************************************
+//
+//****************************************************************************************************************************************************
+void TestWorker::run() {
+ emit started();
+
+ QElapsedTimer timer;
+ timer.start();
+ while (true) {
+ if (cancelled_.loadRelaxed()) {
+ emit cancelled();
+ return;
+ }
+ if (timer.elapsed() >= lifetimeMs_) {
+ break;
+ }
+ }
+
+ if (willSucceed_) {
+ emit finished();
+ } else {
+ emit error(QString());
+ }
+}
+
+
+//****************************************************************************************************************************************************
+//
+//****************************************************************************************************************************************************
+void TestWorker::cancel() {
+ cancelled_.storeRelaxed(1);
+}
+
+
+//****************************************************************************************************************************************************
+//
+//****************************************************************************************************************************************************
+TEST_F(Workers, SuccessfulWorker) {
+ worker_->setLifetime(10);
+ worker_->setWillSucceed(true);
+
+ EXPECT_NO_THROW(overseer_->startWorker(false));
+ EXPECT_NO_THROW(loop_.exec());
+
+ EXPECT_TRUE(results_.started);
+ EXPECT_TRUE(results_.finished);
+ EXPECT_FALSE(results_.error);
+ EXPECT_FALSE(results_.cancelled);
+
+ EXPECT_TRUE(overseer_->worker() != nullptr); // overseer started without autorelease.
+}
+
+
+//****************************************************************************************************************************************************
+//
+//****************************************************************************************************************************************************
+TEST_F(Workers, ErrorWorker) {
+ worker_->setLifetime(10);
+ worker_->setWillSucceed(false);
+
+ EXPECT_NO_THROW(overseer_->startWorker(true));
+ EXPECT_NO_THROW(loop_.exec());
+
+ EXPECT_TRUE(results_.started);
+ EXPECT_FALSE(results_.finished);
+ EXPECT_TRUE(results_.error);
+ EXPECT_FALSE(results_.cancelled);
+
+ EXPECT_TRUE(overseer_->worker() == nullptr); // overseer started with autorelease.
+}
+
+//****************************************************************************************************************************************************
+//
+//****************************************************************************************************************************************************
+TEST_F(Workers, CancelledWorker) {
+ worker_->setLifetime(10000);
+ worker_->setWillSucceed(true);
+ EXPECT_NO_THROW(overseer_->startWorker(false));
+ EXPECT_NO_THROW(QTimer::singleShot(10, [&]() { worker_->cancel(); }));
+
+ EXPECT_NO_THROW(loop_.exec());
+
+ EXPECT_TRUE(results_.started);
+ EXPECT_FALSE(results_.finished);
+ EXPECT_FALSE(results_.error);
+ EXPECT_TRUE(results_.cancelled);
+}
+
+
+//****************************************************************************************************************************************************
+//
+//****************************************************************************************************************************************************
+TEST_F(Workers, Wait) {
+ worker_->setLifetime(10000);
+ worker_->setWillSucceed(true);
+ overseer_->startWorker(true);
+
+ bool isFinished = false;
+ EXPECT_NO_THROW(isFinished = overseer_->isFinished());
+ EXPECT_FALSE(isFinished);
+
+ EXPECT_NO_THROW(isFinished = overseer_->wait(10));
+ EXPECT_FALSE(isFinished);
+
+ worker_->cancel();
+
+ EXPECT_NO_THROW(isFinished = overseer_->wait(10000));
+ EXPECT_TRUE(isFinished);
+
+ EXPECT_NO_THROW(isFinished = overseer_->isFinished());
+ EXPECT_TRUE(isFinished);
+}
+
+
diff --git a/internal/frontend/bridge-gui/bridgepp/Test/TestWorker.h b/internal/frontend/bridge-gui/bridgepp/Test/TestWorker.h
new file mode 100644
index 00000000..abfde90c
--- /dev/null
+++ b/internal/frontend/bridge-gui/bridgepp/Test/TestWorker.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2023 Proton AG
+//
+// This file is part of Proton Mail Bridge.
+//
+// Proton Mail Bridge is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Proton Mail Bridge is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Proton Mail Bridge. If not, see .
+
+
+#ifndef BRIDGE_GUI_TEST_WORKER_H
+#define BRIDGE_GUI_TEST_WORKER_H
+
+
+#include
+#include
+
+
+//****************************************************************************************************************************************************
+/// \brief Test worker class.
+///
+/// This worker simply waits:
+/// - For a specified amount of time and will succeed (emit finished()) or fail (emit error()) based on its parameters.
+/// - to be cancelled (and will emit cancelled in that case).
+//****************************************************************************************************************************************************
+class TestWorker : public bridgepp::Worker {
+Q_OBJECT
+public: // member functions.
+ TestWorker(); ///< Default constructor.
+ TestWorker(TestWorker const &) = delete; ///< Disabled copy-constructor.
+ TestWorker(TestWorker &&) = delete; ///< Disabled assignment copy-constructor.
+ ~TestWorker() override = default; ///< Destructor.
+ TestWorker &operator=(TestWorker const &) = delete; ///< Disabled assignment operator.
+ TestWorker &operator=(TestWorker &&) = delete; ///< Disabled move assignment operator.
+ void setLifetime(qint64 lifetimeMs); ///< Set the lifetime of the worker.
+ void setWillSucceed(bool willSucceed); ///< Set if the worker will succeed.
+ void run() override; ///< Run the worker.
+ void cancel(); ///< Cancel the worker.
+
+private: // data members
+ qint64 lifetimeMs_ { 10 }; ///< The lifetime of the worker in milliseconds.
+ bool willSucceed_ { true }; ///< Will the worker succeed?
+ QAtomicInteger cancelled_; ///< Has the worker been cancelled.
+};
+
+
+//****************************************************************************************************************************************************
+/// \brief Fixture class for worker tests.
+//****************************************************************************************************************************************************
+class Workers : public testing::Test {
+public: // member functions.
+ Workers(); ///< Default constructor.
+ Workers(Workers const &) = delete; ///< Disabled copy-constructor.
+ Workers(Workers &&) = delete; ///< Disabled assignment copy-constructor.
+ ~Workers() = default; ///< Destructor.
+ Workers &operator=(Workers const &) = delete; ///< Disabled assignment operator.
+ Workers &operator=(Workers &&) = delete; ///< Disabled move assignment operator.
+
+protected: // member functions.
+ void SetUp() override; ///< Setup the fixture.
+ void TearDown() override; ///< Tear down the fixture.
+
+protected: // data type
+ struct Results {
+ bool started { false };
+ bool finished { false };
+ bool error { false };
+ bool cancelled { false };
+ }; ///< Test results data type
+
+protected: // data members
+ QCoreApplication app_; ///< The Qt application required for event loop.
+ bridgepp::UPOverseer overseer_; ///< The overseer for the worker.
+ TestWorker *worker_ { nullptr }; ///< The worker.
+ QEventLoop loop_; ///< The event loop.
+ Results results_; ///< The test results.
+};
+
+
+#endif //BRIDGE_GUI_TEST_WORKER_H