Adds two kinds of coverage, continuing #1's remaining scope:
1. Pure-function tests for mapSshError() and escapeForShellSingleQuotes(),
promoted from private members to public statics purely so tests can
call them without spinning up a process. escapeForShellSingleQuotes()
is the actual security boundary for password auth (it's what stops a
password containing a single quote from breaking out of the askpass
script's quoting), so it gets a real adversarial test, not just a
happy-path one.
2. State-machine tests (connect -> Connected, auth failure -> Failed with
the right mapped message, connection refused -> Failed, input
round-tripping, reconnect) driven against tests/fixtures/fake_ssh.sh,
a small controllable stand-in for the real ssh binary, instead of a
real network/SSH server. This needed one small testability seam: a new
constructor overload that overrides the launched program ("ssh" in
production, the fixture script in tests).
POSIX-only for now: the fixture is a shell script, so the state-machine
tests QSKIP on Windows until an equivalent fixture exists there; the
pure-function tests run everywhere.
RdpSessionBackend coverage is still open -- it's a bigger lift again
(FreeRDP's own event loop, not just a QProcess), left for a follow-up.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ProfileRepository::deleteFolder() removes a folder without ever deleting
the profiles or subfolders inside it -- everything directly under the
deleted folder shifts up to take its place (its parent, or the top level
if it had none), exactly as if that one path segment were removed from
each affected path. This is a labels-only operation (a "folder" is just a
grouping string on each profile, not a container that owns them), so
that's the least-surprising behavior versus silently bulk-deleting saved
connections.
Right-clicking a folder in the tree now offers "Delete Folder"; if it
isn't empty, a confirmation states exactly how many profiles/subfolders
will move and to where.
Covered by 7 new unit tests, which caught the same class of bug fixed in
3fab2f9: the new code's own folder-path remap also bound an unguarded
null QString (a folder moving to root) against the `folder_path NOT
NULL` column.
Fixes#20.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>