diff --git a/src/AppInstallerCLITests/CustomHeader.cpp b/src/AppInstallerCLITests/CustomHeader.cpp index 1651a71647..e299523251 100644 --- a/src/AppInstallerCLITests/CustomHeader.cpp +++ b/src/AppInstallerCLITests/CustomHeader.cpp @@ -41,6 +41,14 @@ namespace })delimiter"); } +// In RestClient.cpp tests +extern RestClient CreateRestClient( + const std::string& restApi, + const std::optional& customHeader, + std::string_view caller, + const Http::HttpClientHelper& helper, + const Authentication::AuthenticationArguments& authArgs = {}); + TEST_CASE("RestClient_CustomHeader", "[RestSource][CustomHeader]") { utility::string_t sample = _XPLATSTR( @@ -55,7 +63,7 @@ TEST_CASE("RestClient_CustomHeader", "[RestSource][CustomHeader]") std::optional customHeader = "Testing custom header"; auto header = std::make_pair<>(CustomHeaderName, JSON::GetUtilityString(customHeader.value())); HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sample, header) }; - RestClient client = RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, std::move(helper), {}); + RestClient client = CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, helper); REQUIRE(client.GetSourceIdentifier() == "Source123"); } @@ -104,7 +112,7 @@ TEST_CASE("RestSourceSearch_CustomHeaderExceedingSize", "[RestSource][CustomHead auto header = std::make_pair<>(CustomHeaderName, JSON::GetUtilityString(customHeader)); HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; - REQUIRE_THROWS_HR(RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, std::move(helper), {}), + REQUIRE_THROWS_HR(CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, helper), APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH); } @@ -122,7 +130,7 @@ TEST_CASE("RestClient_CustomUserAgentHeader", "[RestSource][CustomHeader]") std::string testCaller = "TestCaller"; auto header = std::make_pair<>(web::http::header_names::user_agent, JSON::GetUtilityString(Runtime::GetUserAgent(testCaller))); HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sample, header) }; - RestClient client = RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), {}, testCaller, std::move(helper), {}); + RestClient client = CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), {}, testCaller, helper); REQUIRE(client.GetSourceIdentifier() == "Source123"); } @@ -139,6 +147,6 @@ TEST_CASE("RestClient_DefaultUserAgentHeader", "[RestSource][CustomHeader]") auto header = std::make_pair<>(web::http::header_names::user_agent, JSON::GetUtilityString(Runtime::GetDefaultUserAgent())); HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sample, header) }; - RestClient client = RestClient::Create(utility::conversions::to_utf8string("https://restsource.com/api"), {}, {}, std::move(helper), {}); + RestClient client = CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), {}, {}, helper); REQUIRE(client.GetSourceIdentifier() == "Source123"); } diff --git a/src/AppInstallerCLITests/RestClient.cpp b/src/AppInstallerCLITests/RestClient.cpp index 6921a36d9d..7713d791d5 100644 --- a/src/AppInstallerCLITests/RestClient.cpp +++ b/src/AppInstallerCLITests/RestClient.cpp @@ -17,6 +17,16 @@ using namespace AppInstaller::Repository::Rest::Schema; const std::string TestRestUri = "http://restsource.net"; +RestClient CreateRestClient( + const std::string& restApi, + const std::optional& customHeader, + std::string_view caller, + const Http::HttpClientHelper& helper, + const Authentication::AuthenticationArguments& authArgs = {}) +{ + return RestClient::Create(restApi, customHeader, caller, helper, RestClient::GetInformation(restApi, customHeader, caller, helper), authArgs); +} + TEST_CASE("GetLatestCommonVersion", "[RestSource]") { std::set wingetSupportedContracts = { Version {"1.0.0"}, Version {"1.2.0"} }; @@ -103,7 +113,7 @@ TEST_CASE("GetInformation_Success", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper)); + IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, helper); REQUIRE(information.SourceIdentifier == "Source123"); REQUIRE(information.ServerSupportedVersions.size() == 2); REQUIRE(information.ServerSupportedVersions.at(0) == "1.0.0"); @@ -165,7 +175,7 @@ TEST_CASE("GetInformation_WithAuthenticationInfo_Success", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper)); + IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, helper); REQUIRE(information.SourceIdentifier == "Source123"); REQUIRE(information.ServerSupportedVersions.size() == 1); REQUIRE(information.ServerSupportedVersions.at(0) == "1.7.0"); @@ -208,7 +218,7 @@ TEST_CASE("GetInformation_Fail_AgreementsWithoutIdentifier", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper)), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); } TEST_CASE("GetInformation_Fail_InvalidMicrosoftEntraIdInfo", "[RestSource]") @@ -226,7 +236,7 @@ TEST_CASE("GetInformation_Fail_InvalidMicrosoftEntraIdInfo", "[RestSource]") }})delimiter"); HttpClientHelper helper1{ GetTestRestRequestHandler(web::http::status_codes::OK, sample1) }; - REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper1)), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper1), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); utility::string_t sample2 = _XPLATSTR( R"delimiter({ @@ -245,7 +255,7 @@ TEST_CASE("GetInformation_Fail_InvalidMicrosoftEntraIdInfo", "[RestSource]") }})delimiter"); HttpClientHelper helper2{ GetTestRestRequestHandler(web::http::status_codes::OK, sample2) }; - REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper2)), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper2), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); utility::string_t sample3 = _XPLATSTR( R"delimiter({ @@ -263,7 +273,7 @@ TEST_CASE("GetInformation_Fail_InvalidMicrosoftEntraIdInfo", "[RestSource]") }})delimiter"); HttpClientHelper helper3{ GetTestRestRequestHandler(web::http::status_codes::OK, sample3) }; - REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper3)), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper3), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); utility::string_t sample4 = _XPLATSTR( R"delimiter({ @@ -281,7 +291,7 @@ TEST_CASE("GetInformation_Fail_InvalidMicrosoftEntraIdInfo", "[RestSource]") Authentication::AuthenticationArguments authArgs; authArgs.Mode = Authentication::AuthenticationMode::Silent; Version version_1_7{ "1.7.0" }; - REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, RestClient::GetInformation(TestRestUri, {}, {}, std::move(helper4)), authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); + REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, RestClient::GetInformation(TestRestUri, {}, {}, helper4), authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); } TEST_CASE("RestClientCreate_UnsupportedVersion", "[RestSource]") @@ -296,7 +306,7 @@ TEST_CASE("RestClientCreate_UnsupportedVersion", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - REQUIRE_THROWS_HR(RestClient::Create("https://restsource.com/api", {}, {}, std::move(helper)), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); } TEST_CASE("RestClientCreate_UnsupportedAuthenticationMethod", "[RestSource]") @@ -316,7 +326,7 @@ TEST_CASE("RestClientCreate_UnsupportedAuthenticationMethod", "[RestSource]") HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; Authentication::AuthenticationArguments authArgs; authArgs.Mode = Authentication::AuthenticationMode::Silent; - REQUIRE_THROWS_HR(RestClient::Create("https://restsource.com/api", {}, {}, std::move(helper), std::move(authArgs)), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); + REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper, authArgs), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); } TEST_CASE("RestClientCreate_InvalidAuthenticationArguments", "[RestSource]") @@ -339,7 +349,7 @@ TEST_CASE("RestClientCreate_InvalidAuthenticationArguments", "[RestSource]") HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; Authentication::AuthenticationArguments authArgs; authArgs.Mode = Authentication::AuthenticationMode::Unknown; - REQUIRE_THROWS_HR(RestClient::Create("https://restsource.com/api", {}, {}, std::move(helper), std::move(authArgs)), E_UNEXPECTED); + REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper, authArgs), E_UNEXPECTED); } TEST_CASE("RestClientCreate_1.0_Success", "[RestSource]") @@ -354,7 +364,7 @@ TEST_CASE("RestClientCreate_1.0_Success", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - RestClient client = RestClient::Create(TestRestUri, {}, {}, std::move(helper), {}); + RestClient client = CreateRestClient(TestRestUri, {}, {}, helper); REQUIRE(client.GetSourceIdentifier() == "Source123"); } @@ -391,7 +401,7 @@ TEST_CASE("RestClientCreate_1.1_Success", "[RestSource]") }})delimiter"); HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - RestClient client = RestClient::Create(TestRestUri, {}, {}, std::move(helper)); + RestClient client = CreateRestClient(TestRestUri, {}, {}, helper); REQUIRE(client.GetSourceIdentifier() == "Source123"); auto information = client.GetSourceInformation(); REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); @@ -457,7 +467,7 @@ TEST_CASE("RestClientCreate_1.7_Success", "[RestSource]") Authentication::AuthenticationArguments authArgs; authArgs.Mode = Authentication::AuthenticationMode::Silent; HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - RestClient client = RestClient::Create(TestRestUri, {}, {}, std::move(helper), std::move(authArgs)); + RestClient client = CreateRestClient(TestRestUri, {}, {}, helper, authArgs); REQUIRE(client.GetSourceIdentifier() == "Source123"); auto information = client.GetSourceInformation(); REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); diff --git a/src/AppInstallerCommonCore/Rest.cpp b/src/AppInstallerCommonCore/Rest.cpp index 7766d13cd1..cf0b63f9ee 100644 --- a/src/AppInstallerCommonCore/Rest.cpp +++ b/src/AppInstallerCommonCore/Rest.cpp @@ -7,10 +7,9 @@ namespace AppInstaller::Rest { - utility::string_t GetRestAPIBaseUri(std::string restApiUri) + utility::string_t GetRestAPIBaseUri(std::string uri) { // Trim - std::string uri = restApiUri; if (!uri.empty()) { uri = AppInstaller::Utility::Trim(uri); diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp index c7e796bb16..c7f0bfd79c 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp @@ -30,7 +30,7 @@ namespace AppInstaller::Repository::Rest namespace { - HttpClientHelper::HttpRequestHeaders GetHeaders(std::optional customHeader, std::string_view caller) + HttpClientHelper::HttpRequestHeaders GetHeaders(const std::optional& customHeader, std::string_view caller) { HttpClientHelper::HttpRequestHeaders headers; @@ -135,7 +135,7 @@ namespace AppInstaller::Repository::Rest return *commonVersions.rbegin(); } - Schema::IRestClient::Information RestClient::GetInformation(const std::string& restApi, std::optional customHeader, std::string_view caller, const HttpClientHelper& helper) + Schema::IRestClient::Information RestClient::GetInformation(const std::string& restApi, const std::optional& customHeader, std::string_view caller, const HttpClientHelper& helper) { utility::string_t restEndpoint = AppInstaller::Rest::GetRestAPIBaseUri(restApi); THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL, !AppInstaller::Rest::IsValidUri(restEndpoint)); @@ -185,14 +185,19 @@ namespace AppInstaller::Repository::Rest THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); } - RestClient RestClient::Create(const std::string& restApi, std::optional customHeader, std::string_view caller, const HttpClientHelper& helper, const Authentication::AuthenticationArguments& authArgs) + RestClient RestClient::Create( + const std::string& restApi, + const std::optional& customHeader, + std::string_view caller, + const HttpClientHelper& helper, + const Schema::IRestClient::Information& information, + const Authentication::AuthenticationArguments& authArgs) { utility::string_t restEndpoint = AppInstaller::Rest::GetRestAPIBaseUri(restApi); THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL, !AppInstaller::Rest::IsValidUri(restEndpoint)); auto headers = GetHeaders(customHeader, caller); - IRestClient::Information information = GetInformationInternal(restEndpoint, headers, helper); std::optional latestCommonVersion = GetLatestCommonVersion(information.ServerSupportedVersions, WingetSupportedContracts); THROW_HR_IF(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, !latestCommonVersion); diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.h b/src/AppInstallerRepositoryCore/Rest/RestClient.h index 66d299155c..fc02291eb0 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestClient.h +++ b/src/AppInstallerRepositoryCore/Rest/RestClient.h @@ -28,7 +28,7 @@ namespace AppInstaller::Repository::Rest static std::optional GetLatestCommonVersion(const std::vector& serverSupportedVersions, const std::set& wingetSupportedVersions); // Responsible for getting the source information contracts with minimal validation. Does not try to create a rest interface out of it. - static Schema::IRestClient::Information GetInformation(const std::string& restApi, std::optional customHeader, std::string_view caller, const Http::HttpClientHelper& helper); + static Schema::IRestClient::Information GetInformation(const std::string& restApi, const std::optional& customHeader, std::string_view caller, const Http::HttpClientHelper& helper); static std::unique_ptr GetSupportedInterface( const std::string& restApi, @@ -39,7 +39,13 @@ namespace AppInstaller::Repository::Rest const Http::HttpClientHelper& helper); // Creates the rest client. Full validation performed (just as opening the source) - static RestClient Create(const std::string& restApi, std::optional customHeader, std::string_view caller, const Http::HttpClientHelper& helper, const Authentication::AuthenticationArguments& authArgs = {}); + static RestClient Create( + const std::string& restApi, + const std::optional& customHeader, + std::string_view caller, + const Http::HttpClientHelper& helper, + const Schema::IRestClient::Information& information, + const Authentication::AuthenticationArguments& authArgs = {}); private: RestClient(std::unique_ptr supportedInterface, std::string sourceIdentifier); diff --git a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp index da6cc0850e..b30e18d882 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp @@ -50,7 +50,7 @@ namespace AppInstaller::Repository::Rest std::shared_ptr Open(IProgressCallback&) override { Initialize(); - RestClient restClient = RestClient::Create(m_details.Arg, m_customHeader, m_caller, m_httpClientHelper, m_authArgs); + RestClient restClient = RestClient::Create(m_details.Arg, m_customHeader, m_caller, m_httpClientHelper, m_restClientInformation, m_authArgs); return std::make_shared(m_details, m_information, std::move(restClient)); } @@ -61,28 +61,29 @@ namespace AppInstaller::Repository::Rest [&]() { m_httpClientHelper.SetPinningConfiguration(m_details.CertificatePinningConfiguration); - auto sourceInformation = RestClient::GetInformation(m_details.Arg, m_customHeader, m_caller, m_httpClientHelper); + m_restClientInformation = RestClient::GetInformation(m_details.Arg, m_customHeader, m_caller, m_httpClientHelper); - m_details.Identifier = sourceInformation.SourceIdentifier; + m_details.Identifier = m_restClientInformation.SourceIdentifier; - m_information.UnsupportedPackageMatchFields = sourceInformation.UnsupportedPackageMatchFields; - m_information.RequiredPackageMatchFields = sourceInformation.RequiredPackageMatchFields; - m_information.UnsupportedQueryParameters = sourceInformation.UnsupportedQueryParameters; - m_information.RequiredQueryParameters = sourceInformation.RequiredQueryParameters; + m_information.UnsupportedPackageMatchFields = m_restClientInformation.UnsupportedPackageMatchFields; + m_information.RequiredPackageMatchFields = m_restClientInformation.RequiredPackageMatchFields; + m_information.UnsupportedQueryParameters = m_restClientInformation.UnsupportedQueryParameters; + m_information.RequiredQueryParameters = m_restClientInformation.RequiredQueryParameters; - m_information.SourceAgreementsIdentifier = sourceInformation.SourceAgreementsIdentifier; - for (auto const& agreement : sourceInformation.SourceAgreements) + m_information.SourceAgreementsIdentifier = m_restClientInformation.SourceAgreementsIdentifier; + for (auto const& agreement : m_restClientInformation.SourceAgreements) { m_information.SourceAgreements.emplace_back(agreement.Label, agreement.Text, agreement.Url); } - m_information.Authentication = sourceInformation.Authentication; + m_information.Authentication = m_restClientInformation.Authentication; }); } SourceDetails m_details; Http::HttpClientHelper m_httpClientHelper; SourceInformation m_information; + Schema::IRestClient::Information m_restClientInformation; std::optional m_customHeader; std::string m_caller; Authentication::AuthenticationArguments m_authArgs;