Internal
Public Access
Add RdpSessionBackend test coverage (#1)
RdpSessionBackend can't reasonably get the same fixture-driven state-machine tests SshSessionBackend got: it's driven by FreeRDP's own event loop and a raw worker thread against a real freerdp_connect(), not a QProcess we can point at a stand-in binary. What it does have is a large amount of pure, regression-prone logic -- exactly the kind that already caused a real historical bug here (the X11-keycode/PC-AT-scancode mixup fixed in Milestone 7) -- so that's what gets covered instead. Twelve functions promoted from free functions / private members to public statics purely so tests can call them without a live connection: security-mode/performance-profile normalization, the HiDPI scale-value mapping, desktop-size clamping, both scancode-mapping functions, and the five FreeRDP error-code interpretation functions. UINT32 is surfaced as quint32 in the public signatures to keep FreeRDP/WinPR types out of the header, matching how rdp_freerdp* is already only forward-declared there. 27 test cases, including a couple of direct regression guards: verifying scancodeFromNativeScanCode() is a faithful passthrough to FreeRDP's X11 table (not a reimplementation), and that it does NOT reproduce the old "X11 keycode treated as PC/AT scancode" bug for a documented example key. This closes out #1's originally scoped work (CTest wiring, ProfileRepository, SshSessionBackend, RdpSessionBackend coverage). Deeper state-machine coverage for the two session backends remains future work if ever needed, but isn't blocking here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+61
-59
@@ -89,59 +89,13 @@ constexpr double kDefaultDpi = 96.0;
|
||||
constexpr double kMillimetersPerInch = 25.4;
|
||||
|
||||
#ifdef ORBITHUB_HAS_FREERDP
|
||||
QString normalizedRdpSecurityMode(const QString& value)
|
||||
{
|
||||
const QString mode = value.trimmed();
|
||||
if (mode.compare(QStringLiteral("NLA"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("NLA");
|
||||
}
|
||||
if (mode.compare(QStringLiteral("TLS"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("TLS");
|
||||
}
|
||||
if (mode.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("RDP");
|
||||
}
|
||||
return QStringLiteral("Negotiate");
|
||||
}
|
||||
|
||||
// MS-RDPEDISP restricts DesktopScaleFactor/DeviceScaleFactor to exactly
|
||||
// these three values; FreeRDP's own reference client enforces the same
|
||||
// set (client/common/cmdline.c, parse_scale_options). Anything else is
|
||||
// silently ignored by the server, so map the real, continuous
|
||||
// devicePixelRatio down to the nearest one.
|
||||
UINT32 nearestFreeRdpScaleValue(qreal ratio)
|
||||
{
|
||||
if (ratio <= 1.2) {
|
||||
return 100;
|
||||
}
|
||||
if (ratio <= 1.6) {
|
||||
return 140;
|
||||
}
|
||||
return 180;
|
||||
}
|
||||
|
||||
QString normalizedRdpPerformanceProfile(const QString& value)
|
||||
{
|
||||
const QString profile = value.trimmed();
|
||||
if (profile.compare(QStringLiteral("Best Quality"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("Best Quality");
|
||||
}
|
||||
if (profile.compare(QStringLiteral("Best Performance"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("Best Performance");
|
||||
}
|
||||
if (profile.compare(QStringLiteral("Auto Detect"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("Auto Detect");
|
||||
}
|
||||
return QStringLiteral("Balanced");
|
||||
}
|
||||
|
||||
bool applyRdpSecurityMode(rdpSettings* settings, const QString& mode)
|
||||
{
|
||||
if (settings == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString normalized = normalizedRdpSecurityMode(mode);
|
||||
const QString normalized = RdpSessionBackend::normalizedRdpSecurityMode(mode);
|
||||
|
||||
BOOL rdp = FALSE;
|
||||
BOOL tls = FALSE;
|
||||
@@ -175,7 +129,7 @@ bool applyRdpPerformanceProfile(rdpSettings* settings, const QString& profile)
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString normalized = normalizedRdpPerformanceProfile(profile);
|
||||
const QString normalized = RdpSessionBackend::normalizedRdpPerformanceProfile(profile);
|
||||
UINT32 connectionType = CONNECTION_TYPE_BROADBAND_HIGH;
|
||||
BOOL networkAutoDetect = FALSE;
|
||||
if (normalized == QStringLiteral("Best Quality")) {
|
||||
@@ -875,8 +829,55 @@ BOOL orbitAuthenticateEx(freerdp* instance,
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
UINT32 scancodeFromNativeScanCode(quint32 nativeScanCode)
|
||||
QString RdpSessionBackend::normalizedRdpSecurityMode(const QString& value)
|
||||
{
|
||||
const QString mode = value.trimmed();
|
||||
if (mode.compare(QStringLiteral("NLA"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("NLA");
|
||||
}
|
||||
if (mode.compare(QStringLiteral("TLS"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("TLS");
|
||||
}
|
||||
if (mode.compare(QStringLiteral("RDP"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("RDP");
|
||||
}
|
||||
return QStringLiteral("Negotiate");
|
||||
}
|
||||
|
||||
// MS-RDPEDISP restricts DesktopScaleFactor/DeviceScaleFactor to exactly
|
||||
// these three values; FreeRDP's own reference client enforces the same
|
||||
// set (client/common/cmdline.c, parse_scale_options). Anything else is
|
||||
// silently ignored by the server, so map the real, continuous
|
||||
// devicePixelRatio down to the nearest one.
|
||||
quint32 RdpSessionBackend::nearestFreeRdpScaleValue(qreal ratio)
|
||||
{
|
||||
if (ratio <= 1.2) {
|
||||
return 100;
|
||||
}
|
||||
if (ratio <= 1.6) {
|
||||
return 140;
|
||||
}
|
||||
return 180;
|
||||
}
|
||||
|
||||
QString RdpSessionBackend::normalizedRdpPerformanceProfile(const QString& value)
|
||||
{
|
||||
const QString profile = value.trimmed();
|
||||
if (profile.compare(QStringLiteral("Best Quality"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("Best Quality");
|
||||
}
|
||||
if (profile.compare(QStringLiteral("Best Performance"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("Best Performance");
|
||||
}
|
||||
if (profile.compare(QStringLiteral("Auto Detect"), Qt::CaseInsensitive) == 0) {
|
||||
return QStringLiteral("Auto Detect");
|
||||
}
|
||||
return QStringLiteral("Balanced");
|
||||
}
|
||||
|
||||
quint32 RdpSessionBackend::scancodeFromNativeScanCode(quint32 nativeScanCode)
|
||||
{
|
||||
if (nativeScanCode == 0) {
|
||||
return RDP_SCANCODE_UNKNOWN;
|
||||
@@ -935,10 +936,12 @@ UINT32 scancodeFromNativeScanCode(quint32 nativeScanCode)
|
||||
#endif
|
||||
}
|
||||
|
||||
UINT32 scancodeForQtKey(int key, Qt::KeyboardModifiers modifiers, quint32 nativeScanCode)
|
||||
quint32 RdpSessionBackend::scancodeForQtKey(int key,
|
||||
Qt::KeyboardModifiers modifiers,
|
||||
quint32 nativeScanCode)
|
||||
{
|
||||
const bool keypad = modifiers.testFlag(Qt::KeypadModifier);
|
||||
const UINT32 nativeScancode = scancodeFromNativeScanCode(nativeScanCode);
|
||||
const quint32 nativeScancode = scancodeFromNativeScanCode(nativeScanCode);
|
||||
|
||||
switch (key) {
|
||||
case Qt::Key_Escape:
|
||||
@@ -1163,7 +1166,7 @@ UINT32 scancodeForQtKey(int key, Qt::KeyboardModifiers modifiers, quint32 native
|
||||
}
|
||||
}
|
||||
|
||||
QString mapRdpError(UINT32 code)
|
||||
QString RdpSessionBackend::mapRdpError(quint32 code)
|
||||
{
|
||||
switch (code) {
|
||||
case FREERDP_ERROR_CONNECT_LOGON_FAILURE:
|
||||
@@ -1226,7 +1229,7 @@ QString mapRdpError(UINT32 code)
|
||||
return QStringLiteral("RDP connection failed (0x%1).").arg(code, 8, 16, QChar('0'));
|
||||
}
|
||||
|
||||
bool isExpectedDisconnectCode(UINT32 code)
|
||||
bool RdpSessionBackend::isExpectedDisconnectCode(quint32 code)
|
||||
{
|
||||
switch (code) {
|
||||
case FREERDP_ERROR_SUCCESS:
|
||||
@@ -1256,7 +1259,7 @@ bool isExpectedDisconnectCode(UINT32 code)
|
||||
}
|
||||
}
|
||||
|
||||
bool isExpectedConnectAbortCode(UINT32 code)
|
||||
bool RdpSessionBackend::isExpectedConnectAbortCode(quint32 code)
|
||||
{
|
||||
switch (code) {
|
||||
case FREERDP_ERROR_SUCCESS:
|
||||
@@ -1268,7 +1271,7 @@ bool isExpectedConnectAbortCode(UINT32 code)
|
||||
}
|
||||
}
|
||||
|
||||
QString disconnectMessageForCode(UINT32 code)
|
||||
QString RdpSessionBackend::disconnectMessageForCode(quint32 code)
|
||||
{
|
||||
switch (code) {
|
||||
case FREERDP_ERROR_IDLE_TIMEOUT:
|
||||
@@ -1304,7 +1307,7 @@ QString disconnectMessageForCode(UINT32 code)
|
||||
}
|
||||
}
|
||||
|
||||
QString rdpErrorRaw(UINT32 code)
|
||||
QString RdpSessionBackend::rdpErrorRaw(quint32 code)
|
||||
{
|
||||
const char* name = freerdp_get_last_error_name(code);
|
||||
const QString text = (name != nullptr && name[0] != '\0') ? QString::fromUtf8(name)
|
||||
@@ -1312,7 +1315,6 @@ QString rdpErrorRaw(UINT32 code)
|
||||
return QStringLiteral("%1 (0x%2)").arg(text).arg(code, 8, 16, QChar('0'));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
RdpSessionBackend::RdpSessionBackend(const Profile& profile, QObject* parent)
|
||||
: SessionBackend(profile, parent),
|
||||
@@ -2434,7 +2436,7 @@ void RdpSessionBackend::emitConnectionFailureAsync(const QString& displayMessage
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
int RdpSessionBackend::sanitizeDesktopWidth(int width) const
|
||||
int RdpSessionBackend::sanitizeDesktopWidth(int width)
|
||||
{
|
||||
if (width <= 0) {
|
||||
return kDefaultDesktopWidth;
|
||||
@@ -2442,7 +2444,7 @@ int RdpSessionBackend::sanitizeDesktopWidth(int width) const
|
||||
return qBound(kMinDesktopWidth, width, kMaxDesktopWidth);
|
||||
}
|
||||
|
||||
int RdpSessionBackend::sanitizeDesktopHeight(int height) const
|
||||
int RdpSessionBackend::sanitizeDesktopHeight(int height)
|
||||
{
|
||||
if (height <= 0) {
|
||||
return kDefaultDesktopHeight;
|
||||
|
||||
@@ -19,6 +19,24 @@ public:
|
||||
explicit RdpSessionBackend(const Profile& profile, QObject* parent = nullptr);
|
||||
~RdpSessionBackend() override;
|
||||
|
||||
// Pure, state-free helpers exposed as public statics purely so tests
|
||||
// can exercise them without a live FreeRDP connection. UINT32 values
|
||||
// are surfaced as quint32 here to keep FreeRDP/WinPR types out of this
|
||||
// header (uint32_t is what UINT32 always is on every platform this
|
||||
// project targets).
|
||||
static QString normalizedRdpSecurityMode(const QString& value);
|
||||
static QString normalizedRdpPerformanceProfile(const QString& value);
|
||||
static quint32 nearestFreeRdpScaleValue(qreal ratio);
|
||||
static quint32 scancodeFromNativeScanCode(quint32 nativeScanCode);
|
||||
static quint32 scancodeForQtKey(int key, Qt::KeyboardModifiers modifiers, quint32 nativeScanCode);
|
||||
static QString mapRdpError(quint32 code);
|
||||
static bool isExpectedDisconnectCode(quint32 code);
|
||||
static bool isExpectedConnectAbortCode(quint32 code);
|
||||
static QString disconnectMessageForCode(quint32 code);
|
||||
static QString rdpErrorRaw(quint32 code);
|
||||
static int sanitizeDesktopWidth(int width);
|
||||
static int sanitizeDesktopHeight(int height);
|
||||
|
||||
public slots:
|
||||
void connectSession(const SessionConnectOptions& options) override;
|
||||
void disconnectSession() override;
|
||||
@@ -124,8 +142,6 @@ public:
|
||||
private:
|
||||
void emitStateAsync(SessionState state, const QString& message);
|
||||
void emitConnectionFailureAsync(const QString& displayMessage, const QString& rawMessage);
|
||||
int sanitizeDesktopWidth(int width) const;
|
||||
int sanitizeDesktopHeight(int height) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,3 +17,26 @@ target_compile_definitions(test_ssh_session_backend PRIVATE
|
||||
ORBITHUB_TEST_FIXTURES_DIR="${CMAKE_CURRENT_SOURCE_DIR}/fixtures"
|
||||
)
|
||||
add_test(NAME test_ssh_session_backend COMMAND test_ssh_session_backend)
|
||||
|
||||
if(TARGET freerdp AND TARGET winpr)
|
||||
add_executable(test_rdp_session_backend
|
||||
test_rdp_session_backend.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/rdp_session_backend.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/session_backend.h
|
||||
)
|
||||
target_include_directories(test_rdp_session_backend PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_SOURCE_DIR}/third_party/FreeRDP/include
|
||||
${CMAKE_SOURCE_DIR}/third_party/FreeRDP/winpr/include
|
||||
${CMAKE_BINARY_DIR}/third_party/FreeRDP/include
|
||||
${CMAKE_BINARY_DIR}/third_party/FreeRDP/winpr/include
|
||||
)
|
||||
target_compile_definitions(test_rdp_session_backend PRIVATE ORBITHUB_HAS_FREERDP)
|
||||
target_link_libraries(test_rdp_session_backend PRIVATE Qt6::Core Qt6::Gui Qt6::Test freerdp winpr)
|
||||
if(TARGET freerdp-client)
|
||||
target_link_libraries(test_rdp_session_backend PRIVATE freerdp-client)
|
||||
endif()
|
||||
add_test(NAME test_rdp_session_backend COMMAND test_rdp_session_backend)
|
||||
else()
|
||||
message(STATUS "FreeRDP targets not available -- skipping test_rdp_session_backend")
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
#include "rdp_session_backend.h"
|
||||
|
||||
#include <QTest>
|
||||
|
||||
#include <freerdp/error.h>
|
||||
#include <freerdp/locale/keyboard.h>
|
||||
#include <freerdp/scancode.h>
|
||||
|
||||
class TestRdpSessionBackend : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void normalizedRdpSecurityModeRecognizesKnownValues();
|
||||
void normalizedRdpSecurityModeFallsBackToNegotiate();
|
||||
void normalizedRdpPerformanceProfileRecognizesKnownValues();
|
||||
void normalizedRdpPerformanceProfileFallsBackToBalanced();
|
||||
void nearestFreeRdpScaleValueMapsToLegalValues();
|
||||
|
||||
void sanitizeDesktopWidthClampsToLegalRange();
|
||||
void sanitizeDesktopHeightClampsToLegalRange();
|
||||
|
||||
void scancodeFromNativeScanCodeHandlesZero();
|
||||
#if defined(Q_OS_LINUX)
|
||||
void scancodeFromNativeScanCodeDelegatesToX11TableOnLinux();
|
||||
void scancodeFromNativeScanCodeDoesNotTreatX11KeycodeAsPcAtScancode();
|
||||
#endif
|
||||
|
||||
void scancodeForQtKeyMapsDirectKeys();
|
||||
void scancodeForQtKeyRespectsKeypadModifier();
|
||||
void scancodeForQtKeyDisambiguatesLeftRightModifiers();
|
||||
void scancodeForQtKeyReturnsUnknownForUnhandledKey();
|
||||
|
||||
void mapRdpErrorRecognizesAuthFailureCodes();
|
||||
void mapRdpErrorRecognizesAccountStateCodes();
|
||||
void mapRdpErrorRecognizesNetworkCodes();
|
||||
void mapRdpErrorFallsBackForUnknownCode();
|
||||
|
||||
void isExpectedDisconnectCodeRecognizesBenignCodes();
|
||||
void isExpectedDisconnectCodeRejectsAuthFailure();
|
||||
|
||||
void isExpectedConnectAbortCodeRecognizesCancellation();
|
||||
void isExpectedConnectAbortCodeRejectsAuthFailure();
|
||||
|
||||
void disconnectMessageForCodeRecognizesKnownCodes();
|
||||
void disconnectMessageForCodeFallsBackForUnknownCode();
|
||||
|
||||
void rdpErrorRawIncludesHexCode();
|
||||
};
|
||||
|
||||
void TestRdpSessionBackend::normalizedRdpSecurityModeRecognizesKnownValues()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::normalizedRdpSecurityMode(QStringLiteral("nla")),
|
||||
QStringLiteral("NLA"));
|
||||
QCOMPARE(RdpSessionBackend::normalizedRdpSecurityMode(QStringLiteral(" TLS ")),
|
||||
QStringLiteral("TLS"));
|
||||
QCOMPARE(RdpSessionBackend::normalizedRdpSecurityMode(QStringLiteral("Rdp")),
|
||||
QStringLiteral("RDP"));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::normalizedRdpSecurityModeFallsBackToNegotiate()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::normalizedRdpSecurityMode(QStringLiteral("bogus")),
|
||||
QStringLiteral("Negotiate"));
|
||||
QCOMPARE(RdpSessionBackend::normalizedRdpSecurityMode(QString()),
|
||||
QStringLiteral("Negotiate"));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::normalizedRdpPerformanceProfileRecognizesKnownValues()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::normalizedRdpPerformanceProfile(QStringLiteral("best quality")),
|
||||
QStringLiteral("Best Quality"));
|
||||
QCOMPARE(RdpSessionBackend::normalizedRdpPerformanceProfile(QStringLiteral(" Best Performance ")),
|
||||
QStringLiteral("Best Performance"));
|
||||
QCOMPARE(RdpSessionBackend::normalizedRdpPerformanceProfile(QStringLiteral("auto detect")),
|
||||
QStringLiteral("Auto Detect"));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::normalizedRdpPerformanceProfileFallsBackToBalanced()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::normalizedRdpPerformanceProfile(QStringLiteral("bogus")),
|
||||
QStringLiteral("Balanced"));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::nearestFreeRdpScaleValueMapsToLegalValues()
|
||||
{
|
||||
// MS-RDPEDISP legally permits only {100, 140, 180} -- anything else is
|
||||
// silently ignored by the server.
|
||||
QCOMPARE(RdpSessionBackend::nearestFreeRdpScaleValue(1.0), quint32(100));
|
||||
QCOMPARE(RdpSessionBackend::nearestFreeRdpScaleValue(1.2), quint32(100));
|
||||
QCOMPARE(RdpSessionBackend::nearestFreeRdpScaleValue(1.25), quint32(140));
|
||||
QCOMPARE(RdpSessionBackend::nearestFreeRdpScaleValue(1.6), quint32(140));
|
||||
QCOMPARE(RdpSessionBackend::nearestFreeRdpScaleValue(2.0), quint32(180));
|
||||
QCOMPARE(RdpSessionBackend::nearestFreeRdpScaleValue(3.0), quint32(180));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::sanitizeDesktopWidthClampsToLegalRange()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::sanitizeDesktopWidth(0), 1280);
|
||||
QCOMPARE(RdpSessionBackend::sanitizeDesktopWidth(-100), 1280);
|
||||
QCOMPARE(RdpSessionBackend::sanitizeDesktopWidth(100), 640);
|
||||
QCOMPARE(RdpSessionBackend::sanitizeDesktopWidth(1920), 1920);
|
||||
QCOMPARE(RdpSessionBackend::sanitizeDesktopWidth(99999), 8192);
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::sanitizeDesktopHeightClampsToLegalRange()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::sanitizeDesktopHeight(0), 720);
|
||||
QCOMPARE(RdpSessionBackend::sanitizeDesktopHeight(-100), 720);
|
||||
QCOMPARE(RdpSessionBackend::sanitizeDesktopHeight(100), 360);
|
||||
QCOMPARE(RdpSessionBackend::sanitizeDesktopHeight(1080), 1080);
|
||||
QCOMPARE(RdpSessionBackend::sanitizeDesktopHeight(99999), 4320);
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::scancodeFromNativeScanCodeHandlesZero()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::scancodeFromNativeScanCode(0), quint32(RDP_SCANCODE_UNKNOWN));
|
||||
}
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
void TestRdpSessionBackend::scancodeFromNativeScanCodeDelegatesToX11TableOnLinux()
|
||||
{
|
||||
// Our wrapper must be a faithful passthrough to FreeRDP's own
|
||||
// authoritative X11-keycode table, not a reimplementation of it.
|
||||
const quint32 apostropheKeycode = 0x30;
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
const quint32 expected =
|
||||
static_cast<quint32>(freerdp_keyboard_get_rdp_scancode_from_x11_keycode(apostropheKeycode));
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
QCOMPARE(RdpSessionBackend::scancodeFromNativeScanCode(apostropheKeycode), expected);
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::scancodeFromNativeScanCodeDoesNotTreatX11KeycodeAsPcAtScancode()
|
||||
{
|
||||
// Regression guard for the historical bug this table replaced: X11
|
||||
// keycode 0x30 (apostrophe/quote) must NOT resolve to whatever a naive
|
||||
// "treat the X11 keycode as a PC/AT set-1 scancode" interpretation
|
||||
// would give (PC/AT 0x30 is the B key).
|
||||
const quint32 apostropheKeycode = 0x30;
|
||||
const quint32 naivePcAtInterpretation = MAKE_RDP_SCANCODE(apostropheKeycode, FALSE);
|
||||
QVERIFY(RdpSessionBackend::scancodeFromNativeScanCode(apostropheKeycode)
|
||||
!= naivePcAtInterpretation);
|
||||
}
|
||||
#endif
|
||||
|
||||
void TestRdpSessionBackend::scancodeForQtKeyMapsDirectKeys()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Escape, Qt::NoModifier, 0),
|
||||
quint32(RDP_SCANCODE_ESCAPE));
|
||||
QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_A, Qt::NoModifier, 0),
|
||||
quint32(RDP_SCANCODE_KEY_A));
|
||||
QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_F1, Qt::NoModifier, 0),
|
||||
quint32(RDP_SCANCODE_F1));
|
||||
QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Space, Qt::NoModifier, 0),
|
||||
quint32(RDP_SCANCODE_SPACE));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::scancodeForQtKeyRespectsKeypadModifier()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Insert, Qt::NoModifier, 0),
|
||||
quint32(RDP_SCANCODE_INSERT));
|
||||
QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Insert, Qt::KeypadModifier, 0),
|
||||
quint32(RDP_SCANCODE_NUMPAD0));
|
||||
|
||||
QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Delete, Qt::NoModifier, 0),
|
||||
quint32(RDP_SCANCODE_DELETE));
|
||||
QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Delete, Qt::KeypadModifier, 0),
|
||||
quint32(RDP_SCANCODE_DECIMAL));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::scancodeForQtKeyDisambiguatesLeftRightModifiers()
|
||||
{
|
||||
// With no reliable native scancode (0 -> RDP_SCANCODE_UNKNOWN, which
|
||||
// matches neither side), both Shift and Control must default to their
|
||||
// left variant rather than picking arbitrarily.
|
||||
QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Shift, Qt::NoModifier, 0),
|
||||
quint32(RDP_SCANCODE_LSHIFT));
|
||||
QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_Control, Qt::NoModifier, 0),
|
||||
quint32(RDP_SCANCODE_LCONTROL));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::scancodeForQtKeyReturnsUnknownForUnhandledKey()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::scancodeForQtKey(Qt::Key_MediaPlay, Qt::NoModifier, 0),
|
||||
quint32(RDP_SCANCODE_UNKNOWN));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::mapRdpErrorRecognizesAuthFailureCodes()
|
||||
{
|
||||
const QString expected = QStringLiteral("Authentication failed. Check username and password.");
|
||||
QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_LOGON_FAILURE), expected);
|
||||
QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_WRONG_PASSWORD), expected);
|
||||
QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_ACCESS_DENIED), expected);
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::mapRdpErrorRecognizesAccountStateCodes()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_ACCOUNT_DISABLED),
|
||||
QStringLiteral("Account is disabled."));
|
||||
QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT),
|
||||
QStringLiteral("Account is locked out."));
|
||||
QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED),
|
||||
QStringLiteral("Account has expired."));
|
||||
QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_PASSWORD_EXPIRED),
|
||||
QStringLiteral("Password has expired."));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::mapRdpErrorRecognizesNetworkCodes()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_DNS_NAME_NOT_FOUND),
|
||||
QStringLiteral("Host could not be resolved."));
|
||||
QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_CONNECT_TRANSPORT_FAILED),
|
||||
QStringLiteral("Network transport failed while connecting."));
|
||||
QCOMPARE(RdpSessionBackend::mapRdpError(FREERDP_ERROR_SECURITY_NEGO_CONNECT_FAILED),
|
||||
QStringLiteral("RDP security negotiation failed. Try a different RDP security mode."));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::mapRdpErrorFallsBackForUnknownCode()
|
||||
{
|
||||
// Not a code mapRdpError special-cases; must still return something
|
||||
// non-empty rather than crashing or returning an empty string.
|
||||
const QString result = RdpSessionBackend::mapRdpError(0x7FFFFFFF);
|
||||
QVERIFY(!result.isEmpty());
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::isExpectedDisconnectCodeRecognizesBenignCodes()
|
||||
{
|
||||
QVERIFY(RdpSessionBackend::isExpectedDisconnectCode(FREERDP_ERROR_SUCCESS));
|
||||
QVERIFY(RdpSessionBackend::isExpectedDisconnectCode(FREERDP_ERROR_NONE));
|
||||
QVERIFY(RdpSessionBackend::isExpectedDisconnectCode(FREERDP_ERROR_LOGOFF_BY_USER));
|
||||
QVERIFY(RdpSessionBackend::isExpectedDisconnectCode(FREERDP_ERROR_IDLE_TIMEOUT));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::isExpectedDisconnectCodeRejectsAuthFailure()
|
||||
{
|
||||
// An authentication failure must be treated as a real error, never as
|
||||
// an expected/benign disconnect -- otherwise the user would see no
|
||||
// error message at all for a failed login.
|
||||
QVERIFY(!RdpSessionBackend::isExpectedDisconnectCode(FREERDP_ERROR_CONNECT_LOGON_FAILURE));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::isExpectedConnectAbortCodeRecognizesCancellation()
|
||||
{
|
||||
QVERIFY(RdpSessionBackend::isExpectedConnectAbortCode(FREERDP_ERROR_CONNECT_CANCELLED));
|
||||
QVERIFY(RdpSessionBackend::isExpectedConnectAbortCode(FREERDP_ERROR_SUCCESS));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::isExpectedConnectAbortCodeRejectsAuthFailure()
|
||||
{
|
||||
QVERIFY(!RdpSessionBackend::isExpectedConnectAbortCode(FREERDP_ERROR_CONNECT_LOGON_FAILURE));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::disconnectMessageForCodeRecognizesKnownCodes()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::disconnectMessageForCode(FREERDP_ERROR_IDLE_TIMEOUT),
|
||||
QStringLiteral("RDP session disconnected due to idle timeout."));
|
||||
QCOMPARE(RdpSessionBackend::disconnectMessageForCode(FREERDP_ERROR_LOGOFF_BY_USER),
|
||||
QStringLiteral("RDP session signed out."));
|
||||
QCOMPARE(RdpSessionBackend::disconnectMessageForCode(FREERDP_ERROR_CONNECT_CANCELLED),
|
||||
QStringLiteral("Connection cancelled."));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::disconnectMessageForCodeFallsBackForUnknownCode()
|
||||
{
|
||||
QCOMPARE(RdpSessionBackend::disconnectMessageForCode(0x7FFFFFFF),
|
||||
QStringLiteral("RDP session ended."));
|
||||
}
|
||||
|
||||
void TestRdpSessionBackend::rdpErrorRawIncludesHexCode()
|
||||
{
|
||||
const QString result = RdpSessionBackend::rdpErrorRaw(FREERDP_ERROR_SUCCESS);
|
||||
QVERIFY(result.contains(QStringLiteral("(0x00000000)")));
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(TestRdpSessionBackend)
|
||||
#include "test_rdp_session_backend.moc"
|
||||
Reference in New Issue
Block a user