RestClient.cpp (21571B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "TestCommon.h" 5 #include "TestRestRequestHandler.h" 6 #include <Rest/RestClient.h> 7 #include <Rest/Schema/IRestClient.h> 8 #include <AppInstallerVersions.h> 9 #include <AppInstallerErrors.h> 10 #include <AppInstallerRuntime.h> 11 12 using namespace AppInstaller; 13 using namespace AppInstaller::Http; 14 using namespace AppInstaller::Utility; 15 using namespace AppInstaller::Repository::Rest; 16 using namespace AppInstaller::Repository::Rest::Schema; 17 18 const std::string TestRestUri = "http://restsource.net"; 19 20 RestClient CreateRestClient( 21 const std::string& restApi, 22 const std::optional<std::string>& customHeader, 23 std::string_view caller, 24 const Http::HttpClientHelper& helper, 25 const Authentication::AuthenticationArguments& authArgs = {}) 26 { 27 return RestClient::Create(restApi, customHeader, caller, helper, RestClient::GetInformation(restApi, customHeader, caller, helper), authArgs); 28 } 29 30 TEST_CASE("GetLatestCommonVersion", "[RestSource]") 31 { 32 std::set<AppInstaller::Utility::Version> wingetSupportedContracts = { Version {"1.0.0"}, Version {"1.2.0"} }; 33 std::vector<std::string> versions{ "1.0.0", "2.0.0", "1.2.0" }; 34 std::optional<Version> actual = RestClient::GetLatestCommonVersion(versions, wingetSupportedContracts); 35 REQUIRE(actual); 36 REQUIRE(actual.value().ToString() == "1.2.0"); 37 } 38 39 TEST_CASE("GetLatestCommonVersion_OnlyMajorMinorVersionMatched", "[RestSource]") 40 { 41 std::set<AppInstaller::Utility::Version> wingetSupportedContracts = { Version {"1.0.0"}, Version {"1.2.0"} }; 42 std::vector<std::string> versions{ "1.0.0", "2.0.0", "1.2.1" }; 43 std::optional<Version> actual = RestClient::GetLatestCommonVersion(versions, wingetSupportedContracts); 44 REQUIRE(actual); 45 REQUIRE(actual.value().ToString() == "1.2.0"); 46 } 47 48 TEST_CASE("GetLatestCommonVersion_UnsupportedVersion", "[RestSource]") 49 { 50 std::set<AppInstaller::Utility::Version> wingetSupportedContracts = { Version {"3.0.0"}, Version {"4.2.0"} }; 51 std::vector<std::string> versions{ "1.0.0", "2.0.0" }; 52 std::optional<Version> actual = RestClient::GetLatestCommonVersion(versions, wingetSupportedContracts); 53 REQUIRE(!actual); 54 } 55 56 TEST_CASE("GetSupportedInterface", "[RestSource]") 57 { 58 IRestClient::Information info{ "TestId", { "1.0.0" } }; 59 60 Version version{ "1.0.0" }; 61 REQUIRE(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, version, {})->GetVersion() == version); 62 63 // Update this test to next version so that we don't forget to add to supported versions before rest e2e tests are available. 64 Version invalid{ "1.13.0" }; 65 REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, invalid, {}), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); 66 67 Authentication::AuthenticationArguments authArgs; 68 authArgs.Mode = Authentication::AuthenticationMode::Silent; 69 Version version_1_7{ "1.7.0" }; 70 71 // GetSupportedInterface throws on unknown authentication type. 72 IRestClient::Information infoWithUnknownAuthenticationType{ "TestId", { "1.7.0" } }; 73 infoWithUnknownAuthenticationType.Authentication.Type = Authentication::AuthenticationType::Unknown; 74 REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, infoWithUnknownAuthenticationType, authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); 75 76 // GetSupportedInterface throws on invalid authentication info. 77 IRestClient::Information infoWithInvalidAuthenticationInfo{ "TestId", { "1.7.0" } }; 78 infoWithInvalidAuthenticationInfo.Authentication.Type = Authentication::AuthenticationType::MicrosoftEntraId; 79 REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, infoWithInvalidAuthenticationInfo, authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO); 80 } 81 82 TEST_CASE("GetInformation_Success", "[RestSource]") 83 { 84 utility::string_t sample = _XPLATSTR( 85 R"delimiter({ 86 "Data" : { 87 "SourceIdentifier": "Source123", 88 "ServerSupportedVersions": [ 89 "1.0.0", 90 "1.1.0" 91 ], 92 "SourceAgreements": { 93 "AgreementsIdentifier": "agreementV1", 94 "Agreements": [{ 95 "AgreementLabel": "EULA", 96 "Agreement": "this is store agreement", 97 "AgreementUrl": "https://store.agreement" 98 } 99 ] 100 }, 101 "RequiredQueryParameters": [ 102 "Market" 103 ], 104 "RequiredPackageMatchFields": [ 105 "Market" 106 ], 107 "UnsupportedQueryParameters": [ 108 "Moniker" 109 ], 110 "UnsupportedPackageMatchFields": [ 111 "Moniker" 112 ] 113 }})delimiter"); 114 115 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; 116 IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, helper); 117 REQUIRE(information.SourceIdentifier == "Source123"); 118 REQUIRE(information.ServerSupportedVersions.size() == 2); 119 REQUIRE(information.ServerSupportedVersions.at(0) == "1.0.0"); 120 REQUIRE(information.ServerSupportedVersions.at(1) == "1.1.0"); 121 REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); 122 REQUIRE(information.SourceAgreements.size() == 1); 123 REQUIRE(information.SourceAgreements.at(0).Label == "EULA"); 124 REQUIRE(information.SourceAgreements.at(0).Text == "this is store agreement"); 125 REQUIRE(information.SourceAgreements.at(0).Url == "https://store.agreement"); 126 REQUIRE(information.RequiredQueryParameters.size() == 1); 127 REQUIRE(information.RequiredQueryParameters.at(0) == "Market"); 128 REQUIRE(information.RequiredPackageMatchFields.size() == 1); 129 REQUIRE(information.RequiredPackageMatchFields.at(0) == "Market"); 130 REQUIRE(information.UnsupportedQueryParameters.size() == 1); 131 REQUIRE(information.UnsupportedQueryParameters.at(0) == "Moniker"); 132 REQUIRE(information.UnsupportedPackageMatchFields.size() == 1); 133 REQUIRE(information.UnsupportedPackageMatchFields.at(0) == "Moniker"); 134 REQUIRE(information.Authentication.Type == Authentication::AuthenticationType::None); 135 REQUIRE_FALSE(information.Authentication.MicrosoftEntraIdInfo.has_value()); 136 } 137 138 TEST_CASE("GetInformation_WithAuthenticationInfo_Success", "[RestSource]") 139 { 140 utility::string_t sample = _XPLATSTR( 141 R"delimiter({ 142 "Data" : { 143 "SourceIdentifier": "Source123", 144 "ServerSupportedVersions": [ 145 "1.7.0" 146 ], 147 "SourceAgreements": { 148 "AgreementsIdentifier": "agreementV1", 149 "Agreements": [{ 150 "AgreementLabel": "EULA", 151 "Agreement": "this is store agreement", 152 "AgreementUrl": "https://store.agreement" 153 } 154 ] 155 }, 156 "RequiredQueryParameters": [ 157 "Market" 158 ], 159 "RequiredPackageMatchFields": [ 160 "Market" 161 ], 162 "UnsupportedQueryParameters": [ 163 "Moniker" 164 ], 165 "UnsupportedPackageMatchFields": [ 166 "Moniker" 167 ], 168 "Authentication": { 169 "AuthenticationType": "microsoftEntraId", 170 "MicrosoftEntraIdAuthenticationInfo" : { 171 "Resource": "GUID", 172 "Scope" : "test" 173 } 174 } 175 }})delimiter"); 176 177 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; 178 IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, helper); 179 REQUIRE(information.SourceIdentifier == "Source123"); 180 REQUIRE(information.ServerSupportedVersions.size() == 1); 181 REQUIRE(information.ServerSupportedVersions.at(0) == "1.7.0"); 182 REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); 183 REQUIRE(information.SourceAgreements.size() == 1); 184 REQUIRE(information.SourceAgreements.at(0).Label == "EULA"); 185 REQUIRE(information.SourceAgreements.at(0).Text == "this is store agreement"); 186 REQUIRE(information.SourceAgreements.at(0).Url == "https://store.agreement"); 187 REQUIRE(information.RequiredQueryParameters.size() == 1); 188 REQUIRE(information.RequiredQueryParameters.at(0) == "Market"); 189 REQUIRE(information.RequiredPackageMatchFields.size() == 1); 190 REQUIRE(information.RequiredPackageMatchFields.at(0) == "Market"); 191 REQUIRE(information.UnsupportedQueryParameters.size() == 1); 192 REQUIRE(information.UnsupportedQueryParameters.at(0) == "Moniker"); 193 REQUIRE(information.UnsupportedPackageMatchFields.size() == 1); 194 REQUIRE(information.UnsupportedPackageMatchFields.at(0) == "Moniker"); 195 REQUIRE(information.Authentication.Type == Authentication::AuthenticationType::MicrosoftEntraId); 196 REQUIRE(information.Authentication.MicrosoftEntraIdInfo.has_value()); 197 REQUIRE(information.Authentication.MicrosoftEntraIdInfo->Resource == "GUID"); 198 REQUIRE(information.Authentication.MicrosoftEntraIdInfo->Scope == "test"); 199 } 200 201 TEST_CASE("GetInformation_Fail_AgreementsWithoutIdentifier", "[RestSource]") 202 { 203 utility::string_t sample = _XPLATSTR( 204 R"delimiter({ 205 "Data" : { 206 "SourceIdentifier": "Source123", 207 "ServerSupportedVersions": [ 208 "1.0.0", 209 "1.1.0"], 210 "SourceAgreements": { 211 "Agreements": [{ 212 "AgreementLabel": "EULA", 213 "Agreement": "this is store agreement", 214 "AgreementUrl": "https://store.agreement" 215 } 216 ] 217 } 218 }})delimiter"); 219 220 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; 221 REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); 222 } 223 224 TEST_CASE("GetInformation_Fail_InvalidMicrosoftEntraIdInfo", "[RestSource]") 225 { 226 utility::string_t sample1 = _XPLATSTR( 227 R"delimiter({ 228 "Data" : { 229 "SourceIdentifier": "Source123", 230 "ServerSupportedVersions": [ 231 "1.7.0" 232 ], 233 "Authentication": { 234 "AuthenticationType": "microsoftEntraId" 235 } 236 }})delimiter"); 237 238 HttpClientHelper helper1{ GetTestRestRequestHandler(web::http::status_codes::OK, sample1) }; 239 REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper1), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); 240 241 utility::string_t sample2 = _XPLATSTR( 242 R"delimiter({ 243 "Data" : { 244 "SourceIdentifier": "Source123", 245 "ServerSupportedVersions": [ 246 "1.7.0" 247 ], 248 "Authentication": { 249 "AuthenticationType": "microsoftEntraId", 250 "MicrosoftEntraIdAuthenticationInfo" : { 251 "Resource": "", 252 "Scope" : "test" 253 } 254 } 255 }})delimiter"); 256 257 HttpClientHelper helper2{ GetTestRestRequestHandler(web::http::status_codes::OK, sample2) }; 258 REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper2), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); 259 260 utility::string_t sample3 = _XPLATSTR( 261 R"delimiter({ 262 "Data" : { 263 "SourceIdentifier": "Source123", 264 "ServerSupportedVersions": [ 265 "1.7.0" 266 ], 267 "Authentication": { 268 "AuthenticationType": "microsoftEntraId", 269 "MicrosoftEntraIdAuthenticationInfo" : { 270 "Scope" : "test" 271 } 272 } 273 }})delimiter"); 274 275 HttpClientHelper helper3{ GetTestRestRequestHandler(web::http::status_codes::OK, sample3) }; 276 REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper3), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); 277 278 utility::string_t sample4 = _XPLATSTR( 279 R"delimiter({ 280 "Data" : { 281 "SourceIdentifier": "Source123", 282 "ServerSupportedVersions": [ 283 "1.7.0" 284 ], 285 "Authentication": { 286 "AuthenticationType": "microsoftEntraIdForAzureBlobStorage" 287 } 288 }})delimiter"); 289 290 HttpClientHelper helper4{ GetTestRestRequestHandler(web::http::status_codes::OK, sample4) }; 291 Authentication::AuthenticationArguments authArgs; 292 authArgs.Mode = Authentication::AuthenticationMode::Silent; 293 Version version_1_7{ "1.7.0" }; 294 REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, RestClient::GetInformation(TestRestUri, {}, {}, helper4), authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); 295 } 296 297 TEST_CASE("RestClientCreate_UnsupportedVersion", "[RestSource]") 298 { 299 utility::string_t sample = _XPLATSTR( 300 R"delimiter({ 301 "Data" : { 302 "SourceIdentifier": "Source123", 303 "ServerSupportedVersions": [ 304 "1.2.0", 305 "2.0.0"] 306 }})delimiter"); 307 308 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; 309 REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); 310 } 311 312 TEST_CASE("RestClientCreate_UnsupportedAuthenticationMethod", "[RestSource]") 313 { 314 utility::string_t sample = _XPLATSTR( 315 R"delimiter({ 316 "Data" : { 317 "SourceIdentifier": "Source123", 318 "ServerSupportedVersions": [ 319 "1.7.0" 320 ], 321 "Authentication": { 322 "AuthenticationType": "unknown" 323 } 324 }})delimiter"); 325 326 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; 327 Authentication::AuthenticationArguments authArgs; 328 authArgs.Mode = Authentication::AuthenticationMode::Silent; 329 REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper, authArgs), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); 330 } 331 332 TEST_CASE("RestClientCreate_InvalidAuthenticationArguments", "[RestSource]") 333 { 334 utility::string_t sample = _XPLATSTR( 335 R"delimiter({ 336 "Data" : { 337 "SourceIdentifier": "Source123", 338 "ServerSupportedVersions": [ 339 "1.7.0" 340 ], 341 "Authentication": { 342 "AuthenticationType": "microsoftEntraId", 343 "MicrosoftEntraIdAuthenticationInfo" : { 344 "Resource" : "test" 345 } 346 } 347 }})delimiter"); 348 349 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; 350 Authentication::AuthenticationArguments authArgs; 351 authArgs.Mode = Authentication::AuthenticationMode::Unknown; 352 REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper, authArgs), E_UNEXPECTED); 353 } 354 355 TEST_CASE("RestClientCreate_1.0_Success", "[RestSource]") 356 { 357 utility::string_t sample = _XPLATSTR( 358 R"delimiter({ 359 "Data" : { 360 "SourceIdentifier": "Source123", 361 "ServerSupportedVersions": [ 362 "1.0.0", 363 "2.0.0"] 364 }})delimiter"); 365 366 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; 367 RestClient client = CreateRestClient(TestRestUri, {}, {}, helper); 368 REQUIRE(client.GetSourceIdentifier() == "Source123"); 369 } 370 371 TEST_CASE("RestClientCreate_1.1_Success", "[RestSource]") 372 { 373 utility::string_t sample = _XPLATSTR( 374 R"delimiter({ 375 "Data" : { 376 "SourceIdentifier": "Source123", 377 "ServerSupportedVersions": [ 378 "1.0.0", 379 "1.1.0"], 380 "SourceAgreements": { 381 "AgreementsIdentifier": "agreementV1", 382 "Agreements": [{ 383 "AgreementLabel": "EULA", 384 "Agreement": "this is store agreement", 385 "AgreementUrl": "https://store.agreement" 386 } 387 ] 388 }, 389 "RequiredQueryParameters": [ 390 "Market" 391 ], 392 "RequiredPackageMatchFields": [ 393 "Market" 394 ], 395 "UnsupportedQueryParameters": [ 396 "Moniker" 397 ], 398 "UnsupportedPackageMatchFields": [ 399 "Moniker" 400 ] 401 }})delimiter"); 402 403 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; 404 RestClient client = CreateRestClient(TestRestUri, {}, {}, helper); 405 REQUIRE(client.GetSourceIdentifier() == "Source123"); 406 auto information = client.GetSourceInformation(); 407 REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); 408 REQUIRE(information.SourceAgreements.size() == 1); 409 REQUIRE(information.SourceAgreements.at(0).Label == "EULA"); 410 REQUIRE(information.SourceAgreements.at(0).Text == "this is store agreement"); 411 REQUIRE(information.SourceAgreements.at(0).Url == "https://store.agreement"); 412 REQUIRE(information.RequiredQueryParameters.size() == 1); 413 REQUIRE(information.RequiredQueryParameters.at(0) == "Market"); 414 REQUIRE(information.RequiredPackageMatchFields.size() == 1); 415 REQUIRE(information.RequiredPackageMatchFields.at(0) == "Market"); 416 REQUIRE(information.UnsupportedQueryParameters.size() == 1); 417 REQUIRE(information.UnsupportedQueryParameters.at(0) == "Moniker"); 418 REQUIRE(information.UnsupportedPackageMatchFields.size() == 1); 419 REQUIRE(information.UnsupportedPackageMatchFields.at(0) == "Moniker"); 420 } 421 422 TEST_CASE("RestClientCreate_1.7_Success", "[RestSource]") 423 { 424 if (Runtime::IsRunningAsSystem()) 425 { 426 WARN("Test does not support running as system. Skipped."); 427 return; 428 } 429 430 utility::string_t sample = _XPLATSTR( 431 R"delimiter({ 432 "Data" : { 433 "SourceIdentifier": "Source123", 434 "ServerSupportedVersions": [ 435 "1.7.0" 436 ], 437 "SourceAgreements": { 438 "AgreementsIdentifier": "agreementV1", 439 "Agreements": [{ 440 "AgreementLabel": "EULA", 441 "Agreement": "this is store agreement", 442 "AgreementUrl": "https://store.agreement" 443 } 444 ] 445 }, 446 "RequiredQueryParameters": [ 447 "Market" 448 ], 449 "RequiredPackageMatchFields": [ 450 "Market" 451 ], 452 "UnsupportedQueryParameters": [ 453 "Moniker" 454 ], 455 "UnsupportedPackageMatchFields": [ 456 "Moniker" 457 ], 458 "Authentication": { 459 "AuthenticationType": "microsoftEntraId", 460 "MicrosoftEntraIdAuthenticationInfo" : { 461 "Resource": "GUID", 462 "Scope" : "test" 463 } 464 } 465 }})delimiter"); 466 467 Authentication::AuthenticationArguments authArgs; 468 authArgs.Mode = Authentication::AuthenticationMode::Silent; 469 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; 470 RestClient client = CreateRestClient(TestRestUri, {}, {}, helper, authArgs); 471 REQUIRE(client.GetSourceIdentifier() == "Source123"); 472 auto information = client.GetSourceInformation(); 473 REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); 474 REQUIRE(information.SourceAgreements.size() == 1); 475 REQUIRE(information.SourceAgreements.at(0).Label == "EULA"); 476 REQUIRE(information.SourceAgreements.at(0).Text == "this is store agreement"); 477 REQUIRE(information.SourceAgreements.at(0).Url == "https://store.agreement"); 478 REQUIRE(information.RequiredQueryParameters.size() == 1); 479 REQUIRE(information.RequiredQueryParameters.at(0) == "Market"); 480 REQUIRE(information.RequiredPackageMatchFields.size() == 1); 481 REQUIRE(information.RequiredPackageMatchFields.at(0) == "Market"); 482 REQUIRE(information.UnsupportedQueryParameters.size() == 1); 483 REQUIRE(information.UnsupportedQueryParameters.at(0) == "Moniker"); 484 REQUIRE(information.UnsupportedPackageMatchFields.size() == 1); 485 REQUIRE(information.UnsupportedPackageMatchFields.at(0) == "Moniker"); 486 REQUIRE(information.Authentication.Type == Authentication::AuthenticationType::MicrosoftEntraId); 487 REQUIRE(information.Authentication.MicrosoftEntraIdInfo.has_value()); 488 REQUIRE(information.Authentication.MicrosoftEntraIdInfo->Resource == "GUID"); 489 REQUIRE(information.Authentication.MicrosoftEntraIdInfo->Scope == "test"); 490 }