RestInterface_1_0.cpp (31398B)
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/Schema/1_0/Interface.h> 7 #include <Rest/Schema/IRestClient.h> 8 #include <AppInstallerVersions.h> 9 #include <AppInstallerErrors.h> 10 #include <winget/ManifestValidation.h> 11 #include <AppInstallerSHA256.h> 12 13 using namespace TestCommon; 14 using namespace AppInstaller::Http; 15 using namespace AppInstaller::Utility; 16 using namespace AppInstaller::Manifest; 17 using namespace AppInstaller::Repository; 18 using namespace AppInstaller::Repository::Rest; 19 using namespace AppInstaller::Repository::Rest::Schema; 20 using namespace AppInstaller::Repository::Rest::Schema::V1_0; 21 22 namespace 23 { 24 const std::string TestRestUriString = "http://restsource.com/api"; 25 26 utility::string_t GetGoodManifest_RequiredFields() 27 { 28 return _XPLATSTR( 29 R"delimiter({ 30 "Data": { 31 "PackageIdentifier": "Foo.Bar", 32 "Versions": [ 33 { 34 "PackageVersion": "5.0.0", 35 "DefaultLocale": { 36 "PackageLocale": "en-us", 37 "Publisher": "Foo", 38 "PackageName": "Bar", 39 "License": "Foo bar license", 40 "ShortDescription": "Foo bar description" 41 }, 42 "Installers": [ 43 { 44 "Architecture": "x64", 45 "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", 46 "InstallerType": "exe", 47 "InstallerUrl": "https://installer.example.com/foobar.exe" 48 } 49 ] 50 } 51 ] 52 } 53 })delimiter"); 54 } 55 56 utility::string_t GetManifestsResponse_MultipleVersions() 57 { 58 return _XPLATSTR( 59 R"delimiter({ 60 "Data": { 61 "PackageIdentifier": "Foo.Bar", 62 "Versions": [ 63 { 64 "PackageVersion": "5.0.0", 65 "DefaultLocale": { 66 "PackageLocale": "en-us", 67 "Publisher": "Foo", 68 "PackageName": "Bar", 69 "License": "Foo bar license", 70 "ShortDescription": "Foo bar description" 71 }, 72 "Installers": [ 73 { 74 "Architecture": "x64", 75 "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", 76 "InstallerType": "exe", 77 "InstallerUrl": "https://installer.example.com/foobar.exe" 78 } 79 ] 80 }, 81 { 82 "PackageVersion": "6.0.0", 83 "DefaultLocale": { 84 "PackageLocale": "en-us", 85 "Publisher": "Foo", 86 "PackageName": "Bar", 87 "License": "Foo bar license", 88 "ShortDescription": "Foo bar description" 89 }, 90 "Installers": [ 91 { 92 "Architecture": "x64", 93 "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", 94 "InstallerType": "exe", 95 "InstallerUrl": "https://installer.example.com/foobar.exe" 96 } 97 ] 98 } 99 ] 100 } 101 })delimiter"); 102 } 103 104 struct GoodManifest_AllFields 105 { 106 utility::string_t GetSampleManifest_AllFields() 107 { 108 return _XPLATSTR( 109 R"delimiter( 110 { 111 "Data": { 112 "PackageIdentifier": "Foo.Bar", 113 "Versions": [ 114 { 115 "PackageVersion": "3.0.0abc", 116 "DefaultLocale": { 117 "PackageLocale": "en-US", 118 "Publisher": "Foo", 119 "PublisherUrl": "http://publisher.net", 120 "PublisherSupportUrl": "http://publisherSupport.net", 121 "PrivacyUrl": "http://packagePrivacyUrl.net", 122 "Author": "FooBar", 123 "PackageName": "Bar", 124 "PackageUrl": "http://packageUrl.net", 125 "License": "Foo Bar License", 126 "LicenseUrl": "http://licenseUrl.net", 127 "Copyright": "Foo Bar Copyright", 128 "CopyrightUrl": "http://copyrightUrl.net", 129 "ShortDescription": "Foo bar is a foo bar.", 130 "Description": "Foo bar is a placeholder.", 131 "Tags": [ 132 "FooBar", 133 "Foo", 134 "Bar" 135 ], 136 "Moniker": "FooBarMoniker" 137 }, 138 "Channel": "", 139 "Locales": [ 140 { 141 "PackageLocale": "fr-Fr", 142 "Publisher": "Foo French", 143 "PublisherUrl": "http://publisher-fr.net", 144 "PublisherSupportUrl": "http://publisherSupport-fr.net", 145 "PrivacyUrl": "http://packagePrivacyUrl-fr.net", 146 "Author": "FooBar French", 147 "PackageName": "Bar", 148 "PackageUrl": "http://packageUrl-fr.net", 149 "License": "Foo Bar License", 150 "LicenseUrl": "http://licenseUrl-fr.net", 151 "Copyright": "Foo Bar Copyright", 152 "CopyrightUrl": "http://copyrightUrl-fr.net", 153 "ShortDescription": "Foo bar is a foo bar French.", 154 "Description": "Foo bar is a placeholder French.", 155 "Tags": [ 156 "FooBarFr", 157 "FooFr", 158 "BarFr" 159 ] 160 } 161 ], 162 "Installers": [ 163 { 164 "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", 165 "InstallerUrl": "http://foobar.exe", 166 "Architecture": "x86", 167 "InstallerLocale": "en-US", 168 "Platform": [ 169 "Windows.Desktop" 170 ], 171 "MinimumOSVersion": "1078", 172 "InstallerType": "msix", 173 "Scope": "user", 174 "SignatureSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", 175 "InstallModes": [ 176 "interactive" 177 ], 178 "InstallerSwitches": { 179 "Silent": "/s", 180 "SilentWithProgress": "/s", 181 "Interactive": "/i", 182 "InstallLocation": "C:\\Users\\User1", 183 "Log": "/l", 184 "Upgrade": "/u", 185 "Custom": "/custom" 186 }, 187 "InstallerSuccessCodes": [ 188 0 189 ], 190 "UpgradeBehavior": "install", 191 "Commands": [ 192 "command1" 193 ], 194 "Protocols": [ 195 "protocol1" 196 ], 197 "FileExtensions": [ 198 ".file-extension" 199 ], 200 "Dependencies": { 201 "WindowsFeatures": [ 202 "feature1" 203 ], 204 "WindowsLibraries": [ 205 "library1" 206 ], 207 "PackageDependencies": [ 208 { 209 "PackageIdentifier": "Foo.Baz", 210 "MinimumVersion": "2.0.0" 211 } 212 ], 213 "ExternalDependencies": [ 214 "FooBarBaz" 215 ] 216 }, 217 "PackageFamilyName": "FooBar.PackageFamilyName", 218 "ProductCode": "", 219 "Capabilities": [ 220 "Bluetooth" 221 ], 222 "RestrictedCapabilities": [ 223 "restrictedCapability" 224 ] 225 } 226 ] 227 } 228 ] 229 }, 230 "ContinuationToken": "abcd" 231 })delimiter"); 232 } 233 234 void VerifyLocalizations_AllFields(const Manifest& manifest) 235 { 236 REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); 237 REQUIRE(manifest.DefaultLocalization.Get<Localization::Publisher>() == "Foo"); 238 REQUIRE(manifest.DefaultLocalization.Get<Localization::PublisherUrl>() == "http://publisher.net"); 239 REQUIRE(manifest.DefaultLocalization.Get<Localization::PublisherSupportUrl>() == "http://publisherSupport.net"); 240 REQUIRE(manifest.DefaultLocalization.Get<Localization::PrivacyUrl>() == "http://packagePrivacyUrl.net"); 241 REQUIRE(manifest.DefaultLocalization.Get<Localization::Author>() == "FooBar"); 242 REQUIRE(manifest.DefaultLocalization.Get<Localization::PackageName>() == "Bar"); 243 REQUIRE(manifest.DefaultLocalization.Get<Localization::PackageUrl>() == "http://packageUrl.net"); 244 REQUIRE(manifest.DefaultLocalization.Get<Localization::License>() == "Foo Bar License"); 245 REQUIRE(manifest.DefaultLocalization.Get<Localization::LicenseUrl>() == "http://licenseUrl.net"); 246 REQUIRE(manifest.DefaultLocalization.Get<Localization::Copyright>() == "Foo Bar Copyright"); 247 REQUIRE(manifest.DefaultLocalization.Get<Localization::CopyrightUrl>() == "http://copyrightUrl.net"); 248 REQUIRE(manifest.DefaultLocalization.Get<Localization::ShortDescription>() == "Foo bar is a foo bar."); 249 REQUIRE(manifest.DefaultLocalization.Get<Localization::Description>() == "Foo bar is a placeholder."); 250 REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().size() == 3); 251 REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(0) == "FooBar"); 252 REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(1) == "Foo"); 253 REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(2) == "Bar"); 254 255 REQUIRE(manifest.Localizations.size() == 1); 256 ManifestLocalization frenchLocalization = manifest.Localizations.at(0); 257 REQUIRE(frenchLocalization.Locale == "fr-Fr"); 258 REQUIRE(frenchLocalization.Get<Localization::Publisher>() == "Foo French"); 259 REQUIRE(frenchLocalization.Get<Localization::PublisherUrl>() == "http://publisher-fr.net"); 260 REQUIRE(frenchLocalization.Get<Localization::PublisherSupportUrl>() == "http://publisherSupport-fr.net"); 261 REQUIRE(frenchLocalization.Get<Localization::PrivacyUrl>() == "http://packagePrivacyUrl-fr.net"); 262 REQUIRE(frenchLocalization.Get<Localization::Author>() == "FooBar French"); 263 REQUIRE(frenchLocalization.Get<Localization::PackageName>() == "Bar"); 264 REQUIRE(frenchLocalization.Get<Localization::PackageUrl>() == "http://packageUrl-fr.net"); 265 REQUIRE(frenchLocalization.Get<Localization::License>() == "Foo Bar License"); 266 REQUIRE(frenchLocalization.Get<Localization::LicenseUrl>() == "http://licenseUrl-fr.net"); 267 REQUIRE(frenchLocalization.Get<Localization::Copyright>() == "Foo Bar Copyright"); 268 REQUIRE(frenchLocalization.Get<Localization::CopyrightUrl>() == "http://copyrightUrl-fr.net"); 269 REQUIRE(frenchLocalization.Get<Localization::ShortDescription>() == "Foo bar is a foo bar French."); 270 REQUIRE(frenchLocalization.Get<Localization::Description>() == "Foo bar is a placeholder French."); 271 REQUIRE(frenchLocalization.Get<Localization::Tags>().size() == 3); 272 REQUIRE(frenchLocalization.Get<Localization::Tags>().at(0) == "FooBarFr"); 273 REQUIRE(frenchLocalization.Get<Localization::Tags>().at(1) == "FooFr"); 274 REQUIRE(frenchLocalization.Get<Localization::Tags>().at(2) == "BarFr"); 275 } 276 277 void VerifyInstallers_AllFields(const Manifest& manifest) 278 { 279 REQUIRE(manifest.Installers.size() == 1); 280 281 ManifestInstaller actualInstaller = manifest.Installers.at(0); 282 REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); 283 REQUIRE(actualInstaller.Url == "http://foobar.exe"); 284 REQUIRE(actualInstaller.Arch == Architecture::X86); 285 REQUIRE(actualInstaller.Locale == "en-US"); 286 REQUIRE(actualInstaller.Platform.size() == 1); 287 REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); 288 REQUIRE(actualInstaller.MinOSVersion == "1078"); 289 REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Msix); 290 REQUIRE(actualInstaller.Scope == ScopeEnum::User); 291 REQUIRE(actualInstaller.SignatureSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); 292 REQUIRE(actualInstaller.InstallModes.size() == 1); 293 REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); 294 REQUIRE(actualInstaller.Switches.size() == 7); 295 REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); 296 REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); 297 REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); 298 REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); 299 REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); 300 REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); 301 REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); 302 REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); 303 REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); 304 REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Install); 305 REQUIRE(actualInstaller.Commands.at(0) == "command1"); 306 REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); 307 REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); 308 REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); 309 REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); 310 REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); 311 REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); 312 REQUIRE(actualInstaller.PackageFamilyName == "FooBar.PackageFamilyName"); 313 REQUIRE(actualInstaller.ProductCode == ""); 314 REQUIRE(actualInstaller.Capabilities.at(0) == "Bluetooth"); 315 REQUIRE(actualInstaller.RestrictedCapabilities.at(0) == "restrictedCapability"); 316 } 317 }; 318 } 319 320 TEST_CASE("Search_GoodResponse", "[RestSource][Interface_1_0]") 321 { 322 utility::string_t sample = _XPLATSTR( 323 R"delimiter({ 324 "Data" : [ 325 { 326 "PackageIdentifier": "git.package", 327 "PackageName": "package", 328 "Publisher": "git", 329 "Versions": [ 330 { "PackageVersion": "1.0.0" }, 331 { "PackageVersion": "2.0.0" }] 332 }] 333 })delimiter"); 334 335 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; 336 Interface v1{ TestRestUriString, std::move(helper) }; 337 Schema::IRestClient::SearchResult searchResponse = v1.Search({}); 338 REQUIRE(searchResponse.Matches.size() == 1); 339 Schema::IRestClient::Package package = searchResponse.Matches.at(0); 340 REQUIRE(package.PackageInformation.PackageIdentifier.compare("git.package") == 0); 341 REQUIRE(package.PackageInformation.Publisher.compare("git") == 0); 342 REQUIRE(package.PackageInformation.PackageName.compare("package") == 0); 343 REQUIRE(package.Versions.size() == 2); 344 REQUIRE(package.Versions.at(0).VersionAndChannel.GetVersion().ToString().compare("1.0.0") == 0); 345 REQUIRE(package.Versions.at(1).VersionAndChannel.GetVersion().ToString().compare("2.0.0") == 0); 346 } 347 348 TEST_CASE("Search_GoodResponse_AllFields", "[RestSource][Interface_1_0]") 349 { 350 utility::string_t sample = _XPLATSTR( 351 R"delimiter({ 352 "Data" : [ 353 { 354 "PackageIdentifier": "git.package", 355 "PackageName": "package", 356 "Publisher": "git", 357 "Versions": [ 358 { 359 "PackageVersion": "1.0.0", 360 "PackageFamilyNames" : [ 361 "pfn1", 362 "pfn2", 363 "pfn2" 364 ], 365 "ProductCodes" : [ 366 "pc1", 367 "pc2" 368 ] 369 }] 370 }] 371 })delimiter"); 372 373 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; 374 Interface v1{ TestRestUriString, std::move(helper) }; 375 Schema::IRestClient::SearchResult searchResponse = v1.Search({}); 376 REQUIRE(searchResponse.Matches.size() == 1); 377 Schema::IRestClient::Package package = searchResponse.Matches.at(0); 378 REQUIRE(package.PackageInformation.PackageIdentifier.compare("git.package") == 0); 379 REQUIRE(package.PackageInformation.Publisher.compare("git") == 0); 380 REQUIRE(package.PackageInformation.PackageName.compare("package") == 0); 381 REQUIRE(package.Versions.size() == 1); 382 REQUIRE(package.Versions.at(0).VersionAndChannel.GetVersion().ToString().compare("1.0.0") == 0); 383 REQUIRE(package.Versions.at(0).PackageFamilyNames.size() == 2); 384 REQUIRE(package.Versions.at(0).PackageFamilyNames.at(0) == "pfn1"); 385 REQUIRE(package.Versions.at(0).PackageFamilyNames.at(1) == "pfn2"); 386 REQUIRE(package.Versions.at(0).ProductCodes.at(0) == "pc1"); 387 REQUIRE(package.Versions.at(0).ProductCodes.at(1) == "pc2"); 388 } 389 390 TEST_CASE("Search_GoodResponse_404AsEmpty", "[RestSource][Interface_1_0]") 391 { 392 utility::string_t notFoundResponse = _XPLATSTR( 393 R"delimiter({"code":"DataNotFound","data":[],"details":[],"innererror":{"code":"DataNotFound","data":[],"details":[],"message":"Product is not present","source":"StoreEdgeFD"},"message":"Product is not present","source":"StoreEdgeFD"})delimiter"); 394 395 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound, std::move(notFoundResponse)) }; 396 Interface v1{ TestRestUriString, std::move(helper) }; 397 Schema::IRestClient::SearchResult searchResponse = v1.Search({}); 398 REQUIRE(searchResponse.Matches.size() == 0); 399 } 400 401 TEST_CASE("Search_ContinuationToken", "[RestSource][Interface_1_0]") 402 { 403 utility::string_t sample = _XPLATSTR( 404 R"delimiter({ 405 "Data" : [ 406 { 407 "PackageIdentifier": "git.package", 408 "PackageName": "package", 409 "Publisher": "git", 410 "Versions": [ 411 { "PackageVersion": "1.0.0" }] 412 }, 413 { 414 "PackageIdentifier": "foo.package", 415 "PackageName": "package", 416 "Publisher": "foo", 417 "Versions": [ 418 { "PackageVersion": "1.0.0" }] 419 }], 420 "ContinuationToken" : "abcd-ct=" 421 })delimiter"); 422 423 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; 424 Interface v1{ TestRestUriString, std::move(helper) }; 425 SearchRequest request{}; 426 request.MaximumResults = 9; 427 Schema::IRestClient::SearchResult results = v1.Search(request); 428 REQUIRE(results.Matches.size() == request.MaximumResults); 429 430 SearchRequest requestWithSize1{}; 431 requestWithSize1.MaximumResults = 1; 432 Schema::IRestClient::SearchResult resultsWithSize1 = v1.Search(requestWithSize1); 433 REQUIRE(resultsWithSize1.Matches.size() == requestWithSize1.MaximumResults); 434 } 435 436 TEST_CASE("Search_BadResponse_NoVersions", "[RestSource][Interface_1_0]") 437 { 438 utility::string_t sample = _XPLATSTR( 439 R"delimiter({ 440 "Data" : [ 441 { 442 "PackageIdentifier": "git.package", 443 "PackageName": "package", 444 "Publisher": "git", 445 "Versions": null }] 446 })delimiter"); 447 448 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; 449 Interface v1{ TestRestUriString, std::move(helper) }; 450 REQUIRE_THROWS_HR(v1.Search({}), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); 451 } 452 453 TEST_CASE("Search_BadResponse_NotFoundCode", "[RestSource][Interface_1_0]") 454 { 455 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) }; 456 Interface v1{ TestRestUriString, std::move(helper) }; 457 REQUIRE_THROWS_HR(v1.Search({}), APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); 458 } 459 460 TEST_CASE("Search_Optimized_ManifestResponse", "[RestSource][Interface_1_0]") 461 { 462 utility::string_t sample = GetGoodManifest_RequiredFields(); 463 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; 464 AppInstaller::Repository::SearchRequest request; 465 PackageMatchFilter filter{ PackageMatchField::Id, MatchType::Exact, "Foo" }; 466 request.Filters.emplace_back(std::move(filter)); 467 Interface v1{ TestRestUriString, std::move(helper) }; 468 Schema::IRestClient::SearchResult result = v1.Search(request); 469 REQUIRE(result.Matches.size() == 1); 470 REQUIRE(result.Matches[0].Versions.size() == 1); 471 REQUIRE(result.Matches[0].Versions[0].VersionAndChannel.GetVersion().ToString() == "5.0.0"); 472 REQUIRE(result.Matches[0].Versions[0].VersionAndChannel.GetChannel().ToString() == ""); 473 REQUIRE(result.Matches[0].Versions[0].Manifest); 474 475 // Verify manifest is populated 476 Manifest manifest = result.Matches[0].Versions[0].Manifest.value(); 477 REQUIRE(manifest.Id == "Foo.Bar"); 478 REQUIRE(manifest.Version == "5.0.0"); 479 REQUIRE(manifest.DefaultLocalization.Locale == "en-us"); 480 REQUIRE(manifest.DefaultLocalization.Get<Localization::Publisher>() == "Foo"); 481 REQUIRE(manifest.DefaultLocalization.Get<Localization::PackageName>() == "Bar"); 482 REQUIRE(manifest.DefaultLocalization.Get<Localization::License>() == "Foo bar license"); 483 REQUIRE(manifest.DefaultLocalization.Get<Localization::ShortDescription>() == "Foo bar description"); 484 REQUIRE(manifest.Installers.size() == 1); 485 REQUIRE(manifest.Installers[0].Arch == Architecture::X64); 486 REQUIRE(manifest.Installers[0].Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); 487 REQUIRE(manifest.Installers[0].BaseInstallerType == InstallerTypeEnum::Exe); 488 REQUIRE(manifest.Installers[0].Url == "https://installer.example.com/foobar.exe"); 489 } 490 491 TEST_CASE("Search_Optimized_ManifestResponse_MultipleVersions", "[RestSource][Interface_1_0]") 492 { 493 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) }; 494 AppInstaller::Repository::SearchRequest request; 495 PackageMatchFilter filter{ PackageMatchField::Id, MatchType::Exact, "Foo.Bar" }; 496 request.Filters.emplace_back(std::move(filter)); 497 Interface v1{ TestRestUriString, std::move(helper) }; 498 Schema::IRestClient::SearchResult result = v1.Search(request); 499 REQUIRE(result.Matches.size() == 1); 500 REQUIRE(result.Matches[0].Versions.size() == 2); 501 REQUIRE(result.Matches[0].Versions[0].VersionAndChannel.GetVersion().ToString() == "5.0.0"); 502 REQUIRE(result.Matches[0].Versions[0].Manifest); 503 REQUIRE(result.Matches[0].Versions[1].VersionAndChannel.GetVersion().ToString() == "6.0.0"); 504 REQUIRE(result.Matches[0].Versions[1].Manifest); 505 } 506 507 TEST_CASE("Search_Optimized_NoResponse_NotFoundCode", "[RestSource][Interface_1_0]") 508 { 509 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) }; 510 AppInstaller::Repository::SearchRequest request; 511 PackageMatchFilter filter{ PackageMatchField::Id, MatchType::Exact, "Foo" }; 512 request.Filters.emplace_back(std::move(filter)); 513 Interface v1{ TestRestUriString, std::move(helper) }; 514 REQUIRE_THROWS_HR(v1.Search(request), APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); 515 } 516 517 TEST_CASE("GetManifests_GoodResponse", "[RestSource][Interface_1_0]") 518 { 519 GoodManifest_AllFields sampleManifest; 520 utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); 521 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; 522 Interface v1{ TestRestUriString, std::move(helper) }; 523 std::vector<Manifest> manifests = v1.GetManifests("Foo.Bar"); 524 REQUIRE(manifests.size() == 1); 525 526 // Verify manifest is populated 527 Manifest manifest = manifests[0]; 528 REQUIRE(manifest.Id == "Foo.Bar"); 529 REQUIRE(manifest.Version == "3.0.0abc"); 530 REQUIRE(manifest.Moniker == "FooBarMoniker"); 531 REQUIRE(manifest.Channel == ""); 532 REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.0.0" }); 533 sampleManifest.VerifyLocalizations_AllFields(manifest); 534 sampleManifest.VerifyInstallers_AllFields(manifest); 535 } 536 537 TEST_CASE("GetManifests_GoodResponse_404AsEmpty", "[RestSource][Interface_1_0]") 538 { 539 utility::string_t notFoundResponse = _XPLATSTR( 540 R"delimiter({"code":"DataNotFound","data":[],"details":[],"innererror":{"code":"DataNotFound","data":[],"details":[],"message":"Product is not present","source":"StoreEdgeFD"},"message":"Product is not present","source":"StoreEdgeFD"})delimiter"); 541 542 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound, std::move(notFoundResponse)) }; 543 Interface v1{ TestRestUriString, std::move(helper) }; 544 std::vector<Manifest> manifests = v1.GetManifests("Foo.Bar"); 545 REQUIRE(manifests.size() == 0); 546 } 547 548 TEST_CASE("GetManifests_GoodResponse_MultipleVersions", "[RestSource][Interface_1_0]") 549 { 550 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) }; 551 Interface v1{ TestRestUriString, std::move(helper) }; 552 553 // GetManifests 554 std::vector<Manifest> manifests = v1.GetManifests("Foo.Bar"); 555 REQUIRE(manifests.size() == 2); 556 REQUIRE(manifests[0].Version == "5.0.0"); 557 REQUIRE(manifests[1].Version == "6.0.0"); 558 } 559 560 TEST_CASE("GetManifests_BadResponse_SuccessCode", "[RestSource][Interface_1_0]") 561 { 562 utility::string_t badManifest = _XPLATSTR( 563 R"delimiter({ 564 "Data": { 565 "PackageIdentifier": "Foo.Bar", 566 "Versions": [ 567 { 568 "PackageVersion": "5.0.0" 569 } 570 ] 571 } 572 })delimiter"); 573 574 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(badManifest)) }; 575 Interface v1{ TestRestUriString, std::move(helper) }; 576 REQUIRE_THROWS_HR(v1.GetManifests("Foo.Bar"), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); 577 } 578 579 TEST_CASE("GetManifests_NotFoundCode", "[RestSource][Interface_1_0]") 580 { 581 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) }; 582 Interface v1{ TestRestUriString, std::move(helper) }; 583 REQUIRE_THROWS_HR(v1.GetManifests("Foo.Bar"), APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); 584 } 585 586 TEST_CASE("GetManifests_GoodResponse_UnknownInstaller", "[RestSource][Interface_1_0]") 587 { 588 utility::string_t msstoreInstallerResponse = _XPLATSTR( 589 R"delimiter({ 590 "Data": { 591 "PackageIdentifier": "Foo.Bar", 592 "Versions": [ 593 { 594 "PackageVersion": "5.0.0", 595 "DefaultLocale": { 596 "PackageLocale": "en-us", 597 "Publisher": "Foo", 598 "PackageName": "Bar", 599 "License": "Foo bar license", 600 "ShortDescription": "Foo bar description" 601 }, 602 "Installers": [ 603 { 604 "Architecture": "x64", 605 "InstallerType": "msstore", 606 "MSStoreProductIdentifier": "9nblggh4nns1" 607 } 608 ] 609 } 610 ] 611 } 612 })delimiter"); 613 614 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(msstoreInstallerResponse)) }; 615 Interface v1{ TestRestUriString, std::move(helper) }; 616 std::vector<Manifest> manifests = v1.GetManifests("Foo.Bar"); 617 REQUIRE(manifests.size() == 1); 618 619 // Verify manifest is populated and manifest validation passed 620 Manifest& manifest = manifests[0]; 621 REQUIRE(manifest.Installers.size() == 1); 622 REQUIRE(manifest.Installers.at(0).BaseInstallerType == InstallerTypeEnum::Unknown); 623 REQUIRE(manifest.Installers.at(0).ProductId.empty()); 624 } 625 626 TEST_CASE("GetManifestByVersion_GoodResponse_MultipleVersions_VersionFound", "[RestSource][Interface_1_0]") 627 { 628 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) }; 629 Interface v1{ TestRestUriString, std::move(helper) }; 630 631 // GetManifests 632 std::optional<Manifest> manifest = v1.GetManifestByVersion("Foo.Bar", "5.0.0", ""); 633 REQUIRE(manifest.has_value()); 634 REQUIRE(manifest->Version == "5.0.0"); 635 } 636 637 TEST_CASE("GetManifestByVersion_GoodResponse_MultipleVersions_VersionNotFound", "[RestSource][Interface_1_0]") 638 { 639 HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) }; 640 Interface v1{ TestRestUriString, std::move(helper) }; 641 642 // GetManifests 643 std::optional<Manifest> manifest = v1.GetManifestByVersion("Foo.Bar", "7.0.0", ""); 644 REQUIRE_FALSE(manifest.has_value()); 645 }