Wire up CTest and add unit test coverage for ProfileRepository

Adds a Qt6::Test-based unit test target (tests/test_profile_repository.cpp,
21 cases), gated behind an ORBITHUB_BUILD_TESTS option that no-ops
gracefully if Qt6::Test isn't available, so it can't break app-only
builds. Covers profile CRUD, validation rules, search/sort, tag
normalization, and folder handling, each against an isolated temporary
SQLite file (new ProfileRepository(databasePathOverride) constructor
overload added for exactly this).

Caught and fixed a real bug along the way: normalizedTags()'s result was
bound directly without the nonNullTrimmed() null-guard every other field
already uses, so creating a profile with no tags at all hit the `tags
NOT NULL` constraint and silently failed -- including via the Import
Profiles feature for any export where a profile has no tags key.

Partial progress on #1 (RdpSessionBackend/SshSessionBackend state-machine
coverage still open -- much larger lift, needs a testability pass on
those backends first).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 06:39:20 -06:00
co-authored by Claude Sonnet 5
parent 4f5cf8ecd9
commit 3fab2f9de3
5 changed files with 331 additions and 2 deletions
+17 -2
View File
@@ -9,7 +9,11 @@
#include <QVariant>
#include <QStringList>
#include <atomic>
namespace {
std::atomic<int> g_testConnectionCounter{0};
QString buildDatabasePath()
{
QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
@@ -176,7 +180,7 @@ void bindProfileFields(QSqlQuery& query, const Profile& profile)
: QStringLiteral("Negotiate"));
query.addBindValue(isRdp ? normalizedRdpPerformanceProfile(profile.rdpPerformanceProfile)
: QStringLiteral("Balanced"));
query.addBindValue(normalizedTags(profile.tags));
query.addBindValue(nonNullTrimmed(normalizedTags(profile.tags)));
}
Profile profileFromQuery(const QSqlQuery& query)
@@ -259,6 +263,16 @@ ProfileRepository::ProfileRepository() : m_connectionName(QStringLiteral("orbith
}
}
ProfileRepository::ProfileRepository(const QString& databasePathOverride)
: m_connectionName(QStringLiteral("orbithub_test_%1")
.arg(g_testConnectionCounter.fetch_add(1))),
m_databasePathOverride(databasePathOverride)
{
if (!initializeDatabase()) {
QSqlDatabase::removeDatabase(m_connectionName);
}
}
ProfileRepository::~ProfileRepository()
{
if (QSqlDatabase::contains(m_connectionName)) {
@@ -485,7 +499,8 @@ bool ProfileRepository::deleteProfile(qint64 id) const
bool ProfileRepository::initializeDatabase()
{
QSqlDatabase database = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName);
database.setDatabaseName(buildDatabasePath());
database.setDatabaseName(
m_databasePathOverride.isEmpty() ? buildDatabasePath() : m_databasePathOverride);
if (!database.open()) {
m_initError = database.lastError().text();
+4
View File
@@ -35,6 +35,9 @@ class ProfileRepository
{
public:
ProfileRepository();
// databasePathOverride lets tests point the repository at an isolated,
// disposable SQLite file instead of the real app-data location.
explicit ProfileRepository(const QString& databasePathOverride);
~ProfileRepository();
QString initError() const;
@@ -51,6 +54,7 @@ public:
private:
QString m_connectionName;
QString m_databasePathOverride;
QString m_initError;
mutable QString m_lastError;