From 705a373d510102fbce3c75149d005ca533717b10 Mon Sep 17 00:00:00 2001 From: NickVs2015 Date: Wed, 14 Oct 2026 16:52:29 +0300 Subject: [PATCH] feat: cover test gateway service config import (#3239) * feature: cover gateway service config import * fix: use AmneziaApplication in gateway service import test * feature: cover gateway service update * fix: drain gateway workers before test teardown --- .github/workflows/deploy.yml | 15 +- .gitignore | 1 + .../controllers/api/subscriptionController.h | 15 + client/core/controllers/coreController.h | 1 + client/tests/CMakeLists.txt | 2 + client/tests/api/CMakeLists.txt | 10 + client/tests/api/testGatewayServiceImport.cpp | 437 ++++++++++++++++++ client/tests/utils/testCoreController.h | 2 + 8 files changed, 480 insertions(+), 3 deletions(-) create mode 100644 client/tests/api/testGatewayServiceImport.cpp diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d8ac9ca7f..ab1160f9c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1056,6 +1056,7 @@ jobs: - BackupExportImportTest - ConnectionTest - OpenSSLLibraryTest + - GatewayServiceImportTest runs-on: ubuntu-22.04 @@ -1132,6 +1133,8 @@ jobs: -exec chmod +x {} \; - name: Run tests + env: + AMNEZIA_TEST_API_KEY: ${{ secrets.AMNEZIA_TEST_API_KEY }} shell: bash run: | QT_ROOT="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64" @@ -1139,7 +1142,7 @@ jobs: export LD_LIBRARY_PATH="$PWD/deploy/build/client/tests:$QT_ROOT/lib:${LD_LIBRARY_PATH:-}" export QT_QPA_PLATFORM=offscreen - ctest --test-dir deploy/build/client/tests --output-on-failure -VV -R "^${{ matrix.filter }}$" + ctest --test-dir deploy/build/client/tests --output-on-failure -VV --timeout 600 -R "^${{ matrix.filter }}$" # ------------------------------------------------------ @@ -1243,6 +1246,7 @@ jobs: - BackupExportImportTest - ConnectionTest - OpenSSLLibraryTest + - GatewayServiceImportTest runs-on: windows-latest @@ -1309,6 +1313,8 @@ jobs: arch: x64 - name: Run tests + env: + AMNEZIA_TEST_API_KEY: ${{ secrets.AMNEZIA_TEST_API_KEY }} shell: cmd run: | rem Without an attached console (GitHub runner), QTest sends its output to @@ -1318,7 +1324,7 @@ jobs: set PATH=%PATH%;%CD%\client\3rd-prebuilt\deploy-prebuilt\windows\x64 set PATH=%PATH%;%Qt6_DIR%\..\..\..\bin - ctest --test-dir deploy\build\client\tests -C Release -R "${{ matrix.filter }}" --output-on-failure + ctest --test-dir deploy\build\client\tests -C Release -R "${{ matrix.filter }}" --output-on-failure -VV --timeout 600 # ------------------------------------------------------ @@ -1347,6 +1353,7 @@ jobs: - BackupExportImportTest - ConnectionTest - OpenSSLLibraryTest + - GatewayServiceImportTest runs-on: macos-15 @@ -1418,13 +1425,15 @@ jobs: -exec chmod +x {} \; - name: Run tests + env: + AMNEZIA_TEST_API_KEY: ${{ secrets.AMNEZIA_TEST_API_KEY }} shell: bash run: | QT_ROOT="$RUNNER_TEMP/Qt/$QT_VERSION/macos" export DYLD_LIBRARY_PATH="$PWD/deploy/build/client/tests:$QT_ROOT/lib:${DYLD_LIBRARY_PATH:-}" - ctest --test-dir deploy/build/client/tests -R "${{ matrix.filter }}" --output-on-failure + ctest --test-dir deploy/build/client/tests -R "${{ matrix.filter }}" --output-on-failure -VV --timeout 600 # ------------------------------------------------------ diff --git a/.gitignore b/.gitignore index ac487345e..d6dd998df 100644 --- a/.gitignore +++ b/.gitignore @@ -146,3 +146,4 @@ macos-with-sign-build.sh DeveloperIdApplicationCertificate.p12 DeveloperIdInstallerCertificate.p12 +gwenv.sh diff --git a/client/core/controllers/api/subscriptionController.h b/client/core/controllers/api/subscriptionController.h index 88c342aea..948588460 100644 --- a/client/core/controllers/api/subscriptionController.h +++ b/client/core/controllers/api/subscriptionController.h @@ -82,6 +82,21 @@ public: const QString &captchaId, const QString &captchaSolution, CaptchaInfo *retryCaptchaOut = nullptr); +#ifdef AMNEZIA_BUILD_TESTS + // Exposes the gateway response parsing path without performing a network request. + ErrorCode applyImportedServiceConfigForTest(const QString &userCountryCode, const QString &serviceType, + const QString &serviceProtocol, const ProtocolData &protocolData, + const QByteArray &responseBody) + { + return applyImportedServiceConfig(userCountryCode, serviceType, serviceProtocol, protocolData, responseBody); + } + ErrorCode applyUpdatedServiceConfigForTest(const QString &serverId, const QString &serviceProtocol, + const ProtocolData &protocolData, const QByteArray &responseBody) + { + return applyUpdatedServiceConfig(serverId, serviceProtocol, protocolData, responseBody); + } +#endif + private: ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false); bool isApiKeyExpired(const QString &serverId) const; diff --git a/client/core/controllers/coreController.h b/client/core/controllers/coreController.h index 2ebc35ef2..c42f57f31 100644 --- a/client/core/controllers/coreController.h +++ b/client/core/controllers/coreController.h @@ -140,6 +140,7 @@ protected: ServersUiController* serversUiControllerProtected() const { return m_serversUiController; } ServicesCatalogUiController* servicesCatalogUiControllerProtected() const { return m_servicesCatalogUiController; } ApiNewsUiController* apiNewsUiControllerProtected() const { return m_apiNewsUiController; } + SubscriptionController* subscriptionControllerProtected() const { return m_subscriptionController; } private: void initRepositories(); diff --git a/client/tests/CMakeLists.txt b/client/tests/CMakeLists.txt index 941bfc0df..b7aab8d10 100644 --- a/client/tests/CMakeLists.txt +++ b/client/tests/CMakeLists.txt @@ -51,6 +51,8 @@ target_link_libraries(test_common PUBLIC ${LIBS} ) +target_compile_definitions(test_common PUBLIC AMNEZIA_BUILD_TESTS) + target_include_directories(test_common PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/.. diff --git a/client/tests/api/CMakeLists.txt b/client/tests/api/CMakeLists.txt index 1ff160c9c..ab94f19fd 100644 --- a/client/tests/api/CMakeLists.txt +++ b/client/tests/api/CMakeLists.txt @@ -16,7 +16,17 @@ target_link_libraries(test_ui_news_model_and_controller PRIVATE test_common ) +add_executable(test_gateway_service_import + testGatewayServiceImport.cpp +) + +target_link_libraries(test_gateway_service_import PRIVATE + Qt6::Test + test_common +) + add_test(NAME UiApiServicesModelAndControllerTest COMMAND test_ui_api_services_model_and_controller) add_test(NAME UiNewsModelAndControllerTest COMMAND test_ui_news_model_and_controller) +add_test(NAME GatewayServiceImportTest COMMAND test_gateway_service_import) amnezia_copy_runtime_dlls() diff --git a/client/tests/api/testGatewayServiceImport.cpp b/client/tests/api/testGatewayServiceImport.cpp new file mode 100644 index 000000000..68e363c63 --- /dev/null +++ b/client/tests/api/testGatewayServiceImport.cpp @@ -0,0 +1,437 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "utils/testCoreController.h" +#include "core/controllers/api/subscriptionController.h" +#include "core/models/api/apiV2ServerConfig.h" +#include "core/utils/constants/apiKeys.h" +#include "core/utils/constants/configKeys.h" +#include "core/utils/serverConfigUtils.h" + +#include "amneziaApplication.h" +#include "secureQSettings.h" +#include "vpnConnection.h" + +using namespace amnezia; + +namespace +{ + constexpr const char *privateKeyPlaceholder = "$WIREGUARD_CLIENT_PRIVATE_KEY"; + + QJsonObject awgContainer(const QString &clientPrivKeyValue) + { + QJsonObject lastConfig; + lastConfig[configKey::hostName] = QStringLiteral("10.0.0.1"); + lastConfig[configKey::clientPrivKey] = clientPrivKeyValue; + lastConfig[configKey::persistentKeepAlive] = QStringLiteral("25-35"); + + QJsonObject awgConfig; + awgConfig[configKey::lastConfig] = QString(QJsonDocument(lastConfig).toJson(QJsonDocument::Compact)); + awgConfig[configKey::port] = QStringLiteral("35333"); + + QJsonObject container; + container[configKey::container] = QStringLiteral("amnezia-awg"); + container[QString(configKey::awg)] = awgConfig; + return container; + } + + QJsonObject gatewayServerConfig(int configVersion = serverConfigUtils::ConfigSource::AmneziaGateway, + int formatVersion = serverConfigUtils::currentConfigFormatVersion) + { + QJsonObject config; + config[configKey::name] = QStringLiteral("Amnezia Premium"); + config[configKey::description] = QStringLiteral("Premium service"); + config[configKey::hostName] = QStringLiteral("gateway.example.org"); + config[configKey::configVersion] = configVersion; + config[configKey::formatVersion] = formatVersion; + config[configKey::defaultContainer] = QStringLiteral("amnezia-awg"); + config[configKey::containers] = QJsonArray { awgContainer(QString::fromLatin1(privateKeyPlaceholder)) }; + return config; + } + + QJsonObject updatedGatewayServerConfig() + { + QJsonObject config = gatewayServerConfig(); + config[configKey::name] = QStringLiteral("Amnezia Premium Updated"); + config[configKey::hostName] = QStringLiteral("gateway-updated.example.org"); + return config; + } + + // Mirrors how the gateway ships a config in the `config` field of its v1/config response. + QByteArray gatewayResponse(const QJsonObject &serverConfig, bool withServiceInfo = true) + { + const QByteArray payload = qCompress(QJsonDocument(serverConfig).toJson(QJsonDocument::Compact)); + + QJsonObject response; + response[apiDefs::key::config] = + QStringLiteral("vpn://") + QString::fromUtf8(payload.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals)); + if (withServiceInfo) { + QJsonObject serviceInfo; + serviceInfo[apiDefs::key::isAdVisible] = true; + serviceInfo[apiDefs::key::adHeader] = QStringLiteral("Promo header"); + response[apiDefs::key::serviceInfo] = serviceInfo; + } + return QJsonDocument(response).toJson(QJsonDocument::Compact); + } + + // Populated from GitHub Secrets in CI; empty locally and on fork PRs. + QString envValue(const char *name) + { + return QProcessEnvironment::systemEnvironment().value(QString::fromLatin1(name)).trimmed(); + } + + SubscriptionController::ProtocolData awgProtocolData() + { + SubscriptionController::ProtocolData data; + data.wireGuardClientPrivKey = QStringLiteral("cHJpdmF0ZS1rZXktZm9yLXRlc3Q="); + data.wireGuardClientPubKey = QStringLiteral("cHVibGljLWtleS1mb3ItdGVzdA=="); + return data; + } +} // namespace + +class TestGatewayServiceImport : public QObject +{ + Q_OBJECT + +private: + TestCoreController *m_coreController = nullptr; + SecureQSettings *m_settings = nullptr; + + QJsonObject storedServer() const + { + if (m_coreController->m_serversRepository->serversCount() == 0) { + return QJsonObject {}; + } + const QString serverId = m_coreController->m_serversRepository->serverIdAt(0); + const auto config = m_coreController->m_serversRepository->apiV2Config(serverId); + return config ? config->toJson() : QJsonObject {}; + } + + static ApiV2ServerConfig storedSubscription() + { + ApiV2ServerConfig seed = ApiV2ServerConfig::fromJson(gatewayServerConfig()); + seed.apiConfig.serviceType = QStringLiteral("amnezia-premium"); + seed.apiConfig.serviceProtocol = QString(configKey::awg); + seed.apiConfig.userCountryCode = QStringLiteral("NL"); + seed.apiConfig.vpnKey = QStringLiteral("vpn://stored-key"); + seed.authData.apiKey = QStringLiteral("stored-api-key"); + seed.crc = 42; + return seed; + } + + QString addStoredServer(const ApiV2ServerConfig &config) + { + const QJsonObject json = config.toJson(); + return m_coreController->m_serversRepository->addServer(QString(), json, serverConfigUtils::configTypeFromJson(json)); + } + + ErrorCode applyUpdate(const QString &serverId, const QByteArray &response) + { + return m_coreController->m_subscriptionController->applyUpdatedServiceConfigForTest(serverId, configKey::awg, + awgProtocolData(), response); + } + +private slots: + void initTestCase() + { + const QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString(); + m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false); + auto vpnConnection = QSharedPointer::create(nullptr, nullptr); + m_coreController = new TestCoreController(vpnConnection, m_settings, nullptr, this); + } + + void cleanupTestCase() + { + m_settings->clearSettings(); + QTRY_VERIFY_WITH_TIMEOUT(QThreadPool::globalInstance()->activeThreadCount() == 0, 60000); + delete m_coreController; + delete m_settings; + } + + void init() + { + m_settings->clearSettings(); + m_coreController->m_serversRepository->clearServers(); + m_coreController->m_serversModel->updateModel(QVector(), QString {}); + } + + void importsCompressedGatewayConfig() + { + const ErrorCode errorCode = m_coreController->m_subscriptionController->applyImportedServiceConfigForTest( + QStringLiteral("NL"), QStringLiteral("amnezia-premium"), configKey::awg, awgProtocolData(), + gatewayResponse(gatewayServerConfig())); + + QCOMPARE(errorCode, ErrorCode::NoError); + QCOMPARE(m_coreController->m_serversRepository->serversCount(), 1); + + const QJsonObject server = storedServer(); + QCOMPARE(server.value(configKey::name).toString(), QStringLiteral("Amnezia Premium")); + QCOMPARE(server.value(configKey::hostName).toString(), QStringLiteral("gateway.example.org")); + } + + void substitutesWireguardPrivateKey() + { + const SubscriptionController::ProtocolData protocolData = awgProtocolData(); + const ErrorCode errorCode = m_coreController->m_subscriptionController->applyImportedServiceConfigForTest( + QStringLiteral("NL"), QStringLiteral("amnezia-premium"), configKey::awg, protocolData, + gatewayResponse(gatewayServerConfig())); + + QCOMPARE(errorCode, ErrorCode::NoError); + + const QJsonObject container = storedServer().value(configKey::containers).toArray().first().toObject(); + const QJsonObject awgConfig = container.value(QString(configKey::awg)).toObject(); + const QJsonObject lastConfig = + QJsonDocument::fromJson(awgConfig.value(configKey::lastConfig).toString().toUtf8()).object(); + + QCOMPARE(lastConfig.value(configKey::clientPrivKey).toString(), protocolData.wireGuardClientPrivKey); + QVERIFY(!lastConfig.value(configKey::clientPrivKey).toString().contains(privateKeyPlaceholder)); + } + + void keepsAwg3RangeValuesAsStrings() + { + const ErrorCode errorCode = m_coreController->m_subscriptionController->applyImportedServiceConfigForTest( + QStringLiteral("NL"), QStringLiteral("amnezia-premium"), configKey::awg, awgProtocolData(), + gatewayResponse(gatewayServerConfig())); + + QCOMPARE(errorCode, ErrorCode::NoError); + + const QJsonObject container = storedServer().value(configKey::containers).toArray().first().toObject(); + const QJsonObject awgConfig = container.value(QString(configKey::awg)).toObject(); + const QJsonObject lastConfig = + QJsonDocument::fromJson(awgConfig.value(configKey::lastConfig).toString().toUtf8()).object(); + + // awg3 ships ranges such as "25-35"; they must survive import verbatim. + QCOMPARE(lastConfig.value(configKey::persistentKeepAlive).toString(), QStringLiteral("25-35")); + } + + void storesServiceMetadataFromResponse() + { + const ErrorCode errorCode = m_coreController->m_subscriptionController->applyImportedServiceConfigForTest( + QStringLiteral("DE"), QStringLiteral("amnezia-premium"), configKey::awg, awgProtocolData(), + gatewayResponse(gatewayServerConfig())); + + QCOMPARE(errorCode, ErrorCode::NoError); + + const QJsonObject apiConfig = storedServer().value(apiDefs::key::apiConfig).toObject(); + QCOMPARE(apiConfig.value(apiDefs::key::userCountryCode).toString(), QStringLiteral("DE")); + QCOMPARE(apiConfig.value(apiDefs::key::serviceType).toString(), QStringLiteral("amnezia-premium")); + QCOMPARE(apiConfig.value(apiDefs::key::serviceProtocol).toString(), QString(configKey::awg)); + const QJsonObject serviceInfo = apiConfig.value(apiDefs::key::serviceInfo).toObject(); + QCOMPARE(serviceInfo.value(apiDefs::key::isAdVisible).toBool(), true); + QCOMPARE(serviceInfo.value(apiDefs::key::adHeader).toString(), QStringLiteral("Promo header")); + } + + void rejectsEmptyConfigField() + { + QJsonObject response; + response[apiDefs::key::config] = QString(); + + const ErrorCode errorCode = m_coreController->m_subscriptionController->applyImportedServiceConfigForTest( + QStringLiteral("NL"), QStringLiteral("amnezia-premium"), configKey::awg, awgProtocolData(), + QJsonDocument(response).toJson(QJsonDocument::Compact)); + + QCOMPARE(errorCode, ErrorCode::ApiConfigEmptyError); + QCOMPARE(m_coreController->m_serversRepository->serversCount(), 0); + } + + void rejectsConfigWithoutContainers() + { + QJsonObject config = gatewayServerConfig(); + config[configKey::containers] = QJsonArray {}; + + const ErrorCode errorCode = m_coreController->m_subscriptionController->applyImportedServiceConfigForTest( + QStringLiteral("NL"), QStringLiteral("amnezia-premium"), configKey::awg, awgProtocolData(), + gatewayResponse(config)); + + QCOMPARE(errorCode, ErrorCode::ApiConfigEmptyError); + QCOMPARE(m_coreController->m_serversRepository->serversCount(), 0); + } + + void rejectsUnsupportedFormatVersion() + { + const int unsupported = serverConfigUtils::currentConfigFormatVersion + 1; + + const ErrorCode errorCode = m_coreController->m_subscriptionController->applyImportedServiceConfigForTest( + QStringLiteral("NL"), QStringLiteral("amnezia-premium"), configKey::awg, awgProtocolData(), + gatewayResponse(gatewayServerConfig(serverConfigUtils::ConfigSource::AmneziaGateway, unsupported))); + + QCOMPARE(errorCode, ErrorCode::ConfigFormatVersionNotSupportedError); + QCOMPARE(m_coreController->m_serversRepository->serversCount(), 0); + } + + // --- live gateway update (requires AMNEZIA_TEST_API_KEY) --- + + void updatesServiceFromLiveGateway() + { + const QString apiKey = envValue("AMNEZIA_TEST_API_KEY"); + if (apiKey.isEmpty()) { + QSKIP("AMNEZIA_TEST_API_KEY is not set, skipping live update test"); + } + + const QString userCountryCode = QStringLiteral("ru"); + const QString serverCountryCode = QStringLiteral("de"); + + // one fixed device identity for the suite, so test runs never consume new device slots + m_settings->setValue("Conf/installationUuid", + QUuid::createUuidV5(QUuid(), QByteArrayLiteral("amnezia-gateway-service-import-test")) + .toString(QUuid::WithoutBraces)); + + // updateServiceFromGateway builds its payload from the stored subscription, so seed one first. + ApiV2ServerConfig seed = storedSubscription(); + seed.apiConfig.userCountryCode = userCountryCode; + seed.authData.apiKey = apiKey; + const QString serverId = addStoredServer(seed); + QCOMPARE(m_coreController->m_serversRepository->serversCount(), 1); + + SubscriptionController::CaptchaInfo captchaInfo; + const ErrorCode errorCode = m_coreController->m_subscriptionController->updateServiceFromGateway( + serverId, serverCountryCode, /*isConnectEvent=*/false, &captchaInfo, nullptr); + + if (errorCode == ErrorCode::ApiCaptchaRequiredError) { + QSKIP("Gateway requested a captcha, skipping live update test"); + } + + QCOMPARE(errorCode, ErrorCode::NoError); + QCOMPARE(m_coreController->m_serversRepository->serversCount(), 1); + + const QJsonObject server = storedServer(); + QVERIFY(!server.value(configKey::hostName).toString().isEmpty()); + QVERIFY(server.value(configKey::hostName).toString() != QStringLiteral("gateway.example.org")); + QVERIFY(!server.value(configKey::containers).toArray().isEmpty()); + QCOMPARE(server.value(configKey::configVersion).toInt(), int(serverConfigUtils::ConfigSource::AmneziaGateway)); + + const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject(); + QCOMPARE(apiConfig.value(apiDefs::key::serviceType).toString(), seed.apiConfig.serviceType); + QCOMPARE(apiConfig.value(apiDefs::key::serviceProtocol).toString(), seed.apiConfig.serviceProtocol); + QCOMPARE(apiConfig.value(apiDefs::key::userCountryCode).toString(), userCountryCode); + QCOMPARE(apiConfig.value(apiDefs::key::serverCountryCode).toString().toLower(), serverCountryCode); + + // QVERIFY, not QCOMPARE: a mismatch must not print the key into CI logs + const QJsonObject authData = server.value(apiDefs::key::authData).toObject(); + QVERIFY(authData.value(apiDefs::key::apiKey).toString() == apiKey); + } + + void rejectsNonGatewayConfigSource() + { + const ErrorCode errorCode = m_coreController->m_subscriptionController->applyImportedServiceConfigForTest( + QStringLiteral("NL"), QStringLiteral("amnezia-premium"), configKey::awg, awgProtocolData(), + gatewayResponse(gatewayServerConfig(serverConfigUtils::ConfigSource::Telegram))); + + QCOMPARE(errorCode, ErrorCode::InternalError); + QCOMPARE(m_coreController->m_serversRepository->serversCount(), 0); + } + + // --- update of a stored subscription (offline) --- + + void updatesStoredServerInPlace() + { + const QString serverId = addStoredServer(storedSubscription()); + + QCOMPARE(applyUpdate(serverId, gatewayResponse(updatedGatewayServerConfig())), ErrorCode::NoError); + QCOMPARE(m_coreController->m_serversRepository->serversCount(), 1); + QCOMPARE(m_coreController->m_serversRepository->serverIdAt(0), serverId); + + const QJsonObject server = storedServer(); + QCOMPARE(server.value(configKey::name).toString(), QStringLiteral("Amnezia Premium Updated")); + QCOMPARE(server.value(configKey::hostName).toString(), QStringLiteral("gateway-updated.example.org")); + QVERIFY(!server.value(configKey::containers).toArray().isEmpty()); + + const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject(); + QCOMPARE(apiConfig.value(apiDefs::key::serviceType).toString(), QStringLiteral("amnezia-premium")); + QCOMPARE(apiConfig.value(apiDefs::key::serviceProtocol).toString(), QString(configKey::awg)); + QCOMPARE(apiConfig.value(apiDefs::key::userCountryCode).toString(), QStringLiteral("NL")); + QCOMPARE(apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adHeader).toString(), + QStringLiteral("Promo header")); + } + + void preservesCredentialsOnUpdate() + { + const QString serverId = addStoredServer(storedSubscription()); + + QCOMPARE(applyUpdate(serverId, gatewayResponse(updatedGatewayServerConfig())), ErrorCode::NoError); + + const QJsonObject server = storedServer(); + QCOMPARE(server.value(apiDefs::key::authData).toObject().value(apiDefs::key::apiKey).toString(), + QStringLiteral("stored-api-key")); + QCOMPARE(server.value(apiDefs::key::apiConfig).toObject().value(apiDefs::key::vpnKey).toString(), + QStringLiteral("vpn://stored-key")); + QCOMPARE(server.value(configKey::crc).toInt(), 42); + } + + void keepsNameOverriddenByUserOnUpdate() + { + ApiV2ServerConfig seed = storedSubscription(); + seed.name = QStringLiteral("My VPN"); + seed.nameOverriddenByUser = true; + const QString serverId = addStoredServer(seed); + + QCOMPARE(applyUpdate(serverId, gatewayResponse(updatedGatewayServerConfig())), ErrorCode::NoError); + + const QJsonObject server = storedServer(); + QCOMPARE(server.value(configKey::name).toString(), QStringLiteral("My VPN")); + QVERIFY(server.value(configKey::nameOverriddenByUser).toBool()); + QCOMPARE(server.value(configKey::hostName).toString(), QStringLiteral("gateway-updated.example.org")); + } + + void clearsSubscriptionExpiredFlagOnUpdate() + { + ApiV2ServerConfig seed = storedSubscription(); + seed.apiConfig.subscriptionExpiredByServer = true; + const QString serverId = addStoredServer(seed); + QVERIFY(storedServer().value(apiDefs::key::apiConfig).toObject().value(apiDefs::key::subscriptionExpiredByServer).toBool()); + + QCOMPARE(applyUpdate(serverId, gatewayResponse(updatedGatewayServerConfig())), ErrorCode::NoError); + + QVERIFY(!storedServer().value(apiDefs::key::apiConfig).toObject().value(apiDefs::key::subscriptionExpiredByServer).toBool()); + } + + void rejectsUpdateOfUnknownServer() + { + QCOMPARE(applyUpdate(QStringLiteral("missing-server"), gatewayResponse(updatedGatewayServerConfig())), + ErrorCode::InternalError); + QCOMPARE(m_coreController->m_serversRepository->serversCount(), 0); + } + + void rejectsUnsupportedFormatVersionOnUpdate() + { + const QString serverId = addStoredServer(storedSubscription()); + const int unsupported = serverConfigUtils::currentConfigFormatVersion + 1; + + QCOMPARE(applyUpdate(serverId, gatewayResponse(gatewayServerConfig(serverConfigUtils::ConfigSource::AmneziaGateway, unsupported))), + ErrorCode::ConfigFormatVersionNotSupportedError); + + QCOMPARE(m_coreController->m_serversRepository->serversCount(), 1); + QCOMPARE(storedServer().value(configKey::hostName).toString(), QStringLiteral("gateway.example.org")); + } + + void rejectsNonGatewaySourceOnUpdate() + { + const QString serverId = addStoredServer(storedSubscription()); + + QCOMPARE(applyUpdate(serverId, gatewayResponse(gatewayServerConfig(serverConfigUtils::ConfigSource::Telegram))), + ErrorCode::InternalError); + + const QJsonObject server = storedServer(); + QCOMPARE(m_coreController->m_serversRepository->serversCount(), 1); + QCOMPARE(server.value(configKey::hostName).toString(), QStringLiteral("gateway.example.org")); + QCOMPARE(server.value(apiDefs::key::authData).toObject().value(apiDefs::key::apiKey).toString(), + QStringLiteral("stored-api-key")); + } +}; + +int main(int argc, char *argv[]) +{ + // CoreController reaches the gateway through amnApp->networkManager(), so the + // application instance has to really be an AmneziaApplication. + AmneziaApplication app(argc, argv); + TestGatewayServiceImport tc; + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&tc, argc, argv); +} +#include "testGatewayServiceImport.moc" diff --git a/client/tests/utils/testCoreController.h b/client/tests/utils/testCoreController.h index f3090fe22..c03e1e3d4 100644 --- a/client/tests/utils/testCoreController.h +++ b/client/tests/utils/testCoreController.h @@ -39,6 +39,7 @@ public: , m_serversUiController(serversUiControllerProtected()) , m_servicesCatalogUiController(servicesCatalogUiControllerProtected()) , m_apiNewsUiController(apiNewsUiControllerProtected()) + , m_subscriptionController(subscriptionControllerProtected()) { } @@ -71,6 +72,7 @@ public: ServersUiController *m_serversUiController; ServicesCatalogUiController *m_servicesCatalogUiController; ApiNewsUiController *m_apiNewsUiController; + SubscriptionController *m_subscriptionController; }; #endif // TESTCORECONTROLLER_H