commit 4d51b5fb9b1ea5fbc8da1a053423457535e5fec4
parent 30e54e4c3b47d6b9e3de3afaddb2bc0e27365782
Author: yao-msft <50888816+yao-msft@users.noreply.github.com>
Date: Fri, 9 Feb 2024 12:24:52 -0800
Winget client rest source parsing for 1.7 manifest (#4155)
Diffstat:
8 files changed, 490 insertions(+), 11 deletions(-)
diff --git a/src/AppInstallerCLITests/RestInterface_1_7.cpp b/src/AppInstallerCLITests/RestInterface_1_7.cpp
@@ -82,6 +82,361 @@ namespace
]
}
})delimiter");
+
+ struct GoodManifest_AllFields
+ {
+ utility::string_t GetSampleManifest_AllFields()
+ {
+ return _XPLATSTR(
+ R"delimiter(
+ {
+ "Data": {
+ "PackageIdentifier": "Foo.Bar",
+ "Versions": [
+ {
+ "PackageVersion": "3.0.0abc",
+ "DefaultLocale": {
+ "PackageLocale": "en-US",
+ "Publisher": "Foo",
+ "PublisherUrl": "http://publisher.net",
+ "PublisherSupportUrl": "http://publisherSupport.net",
+ "PrivacyUrl": "http://packagePrivacyUrl.net",
+ "Author": "FooBar",
+ "PackageName": "Bar",
+ "PackageUrl": "http://packageUrl.net",
+ "License": "Foo Bar License",
+ "LicenseUrl": "http://licenseUrl.net",
+ "Copyright": "Foo Bar Copyright",
+ "CopyrightUrl": "http://copyrightUrl.net",
+ "ShortDescription": "Foo bar is a foo bar.",
+ "Description": "Foo bar is a placeholder.",
+ "Tags": [
+ "FooBar",
+ "Foo",
+ "Bar"
+ ],
+ "Moniker": "FooBarMoniker",
+ "ReleaseNotes": "Default release notes",
+ "ReleaseNotesUrl": "https://DefaultReleaseNotes.net",
+ "Agreements": [{
+ "AgreementLabel": "DefaultLabel",
+ "Agreement": "DefaultText",
+ "AgreementUrl": "https://DefaultAgreementUrl.net"
+ }],
+ "PurchaseUrl": "http://DefaultPurchaseUrl.net",
+ "InstallationNotes": "Default Installation Notes",
+ "Documentations": [{
+ "DocumentLabel": "Default Document Label",
+ "DocumentUrl": "http://DefaultDocumentUrl.net"
+ }],
+ "Icons": [{
+ "IconUrl": "https://DefaultTestIcon",
+ "IconFileType": "ico",
+ "IconResolution": "custom",
+ "IconTheme": "default",
+ "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123"
+ }]
+ },
+ "Channel": "",
+ "Locales": [
+ {
+ "PackageLocale": "fr-Fr",
+ "Publisher": "Foo French",
+ "PublisherUrl": "http://publisher-fr.net",
+ "PublisherSupportUrl": "http://publisherSupport-fr.net",
+ "PrivacyUrl": "http://packagePrivacyUrl-fr.net",
+ "Author": "FooBar French",
+ "PackageName": "Bar",
+ "PackageUrl": "http://packageUrl-fr.net",
+ "License": "Foo Bar License",
+ "LicenseUrl": "http://licenseUrl-fr.net",
+ "Copyright": "Foo Bar Copyright",
+ "CopyrightUrl": "http://copyrightUrl-fr.net",
+ "ShortDescription": "Foo bar is a foo bar French.",
+ "Description": "Foo bar is a placeholder French.",
+ "Tags": [
+ "FooBarFr",
+ "FooFr",
+ "BarFr"
+ ],
+ "ReleaseNotes": "Release notes",
+ "ReleaseNotesUrl": "https://ReleaseNotes.net",
+ "Agreements": [{
+ "AgreementLabel": "Label",
+ "Agreement": "Text",
+ "AgreementUrl": "https://AgreementUrl.net"
+ }],
+ "PurchaseUrl": "http://purchaseUrl.net",
+ "InstallationNotes": "Installation Notes",
+ "Documentations": [{
+ "DocumentLabel": "Document Label",
+ "DocumentUrl": "http://documentUrl.net"
+ }],
+ "Icons": [{
+ "IconUrl": "https://testIcon",
+ "IconFileType": "png",
+ "IconResolution": "32x32",
+ "IconTheme": "light",
+ "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321"
+ }]
+ }
+ ],)delimiter") _XPLATSTR(R"delimiter(
+ "Installers": [
+ {
+ "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6",
+ "InstallerUrl": "http://foobar.zip",
+ "Architecture": "x86",
+ "InstallerLocale": "en-US",
+ "Platform": [
+ "Windows.Desktop"
+ ],
+ "MinimumOSVersion": "1078",
+ "InstallerType": "zip",
+ "Scope": "user",
+ "InstallModes": [
+ "interactive"
+ ],
+ "InstallerSwitches": {
+ "Silent": "/s",
+ "SilentWithProgress": "/s",
+ "Interactive": "/i",
+ "InstallLocation": "C:\\Users\\User1",
+ "Log": "/l",
+ "Upgrade": "/u",
+ "Custom": "/custom",
+ "Repair": "/repair"
+ },
+ "InstallerSuccessCodes": [
+ 0
+ ],
+ "UpgradeBehavior": "deny",
+ "Commands": [
+ "command1"
+ ],
+ "Protocols": [
+ "protocol1"
+ ],
+ "FileExtensions": [
+ ".file-extension"
+ ],
+ "Dependencies": {
+ "WindowsFeatures": [
+ "feature1"
+ ],
+ "WindowsLibraries": [
+ "library1"
+ ],
+ "PackageDependencies": [
+ {
+ "PackageIdentifier": "Foo.Baz",
+ "MinimumVersion": "2.0.0"
+ }
+ ],
+ "ExternalDependencies": [
+ "FooBarBaz"
+ ]
+ },
+ "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d",
+ "ReleaseDate": "2021-01-01",
+ "InstallerAbortsTerminal": true,
+ "InstallLocationRequired": true,
+ "RequireExplicitUpgrade": true,
+ "UnsupportedOSArchitectures": [ "arm" ],
+ "ElevationRequirement": "elevatesSelf",
+ "AppsAndFeaturesEntries": [{
+ "DisplayName": "DisplayName",
+ "DisplayVersion": "DisplayVersion",
+ "Publisher": "Publisher",
+ "ProductCode": "ProductCode",
+ "UpgradeCode": "UpgradeCode",
+ "InstallerType": "exe"
+ }],
+ "Markets" : {
+ "AllowedMarkets": [ "US" ]
+ },
+ "ExpectedReturnCodes": [{
+ "InstallerReturnCode": 3,
+ "ReturnResponse": "custom",
+ "ReturnResponseUrl": "http://returnResponseUrl.net"
+ }],
+ "NestedInstallerType": "portable",
+ "DisplayInstallWarnings": true,
+ "UnsupportedArguments": [ "log" ],
+ "NestedInstallerFiles": [{
+ "RelativeFilePath": "test\\app.exe",
+ "PortableCommandAlias": "test.exe"
+ }],
+ "InstallationMetadata": {
+ "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation",
+ "Files": [{
+ "RelativeFilePath": "test\\app.exe",
+ "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6",
+ "FileType": "launch",
+ "InvocationParameter": "/parameter",
+ "DisplayName": "test"
+ }]
+ },
+ "DownloadCommandProhibited": true,
+ "RepairBehavior": "uninstaller"
+ }
+ ]
+ }
+ ]
+ },
+ "ContinuationToken": "abcd"
+ })delimiter");
+ }
+
+ void VerifyLocalizations_AllFields(const AppInstaller::Manifest::Manifest& manifest)
+ {
+ REQUIRE(manifest.DefaultLocalization.Locale == "en-US");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Publisher>() == "Foo");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::PublisherUrl>() == "http://publisher.net");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::PublisherSupportUrl>() == "http://publisherSupport.net");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::PrivacyUrl>() == "http://packagePrivacyUrl.net");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Author>() == "FooBar");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::PackageName>() == "Bar");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::PackageUrl>() == "http://packageUrl.net");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::License>() == "Foo Bar License");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::LicenseUrl>() == "http://licenseUrl.net");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Copyright>() == "Foo Bar Copyright");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::CopyrightUrl>() == "http://copyrightUrl.net");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::ShortDescription>() == "Foo bar is a foo bar.");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Description>() == "Foo bar is a placeholder.");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().size() == 3);
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(0) == "FooBar");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(1) == "Foo");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(2) == "Bar");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::ReleaseNotes>() == "Default release notes");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::ReleaseNotesUrl>() == "https://DefaultReleaseNotes.net");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().size() == 1);
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().at(0).Label == "DefaultLabel");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().at(0).AgreementText == "DefaultText");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().at(0).AgreementUrl == "https://DefaultAgreementUrl.net");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::PurchaseUrl>() == "http://DefaultPurchaseUrl.net");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::InstallationNotes>() == "Default Installation Notes");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().size() == 1);
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().at(0).DocumentLabel == "Default Document Label");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().at(0).DocumentUrl == "http://DefaultDocumentUrl.net");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().size() == 1);
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Url == "https://DefaultTestIcon");
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).FileType == IconFileTypeEnum::Ico);
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Resolution == IconResolutionEnum::Custom);
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Theme == IconThemeEnum::Default);
+ REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123"));
+
+ REQUIRE(manifest.Localizations.size() == 1);
+ ManifestLocalization frenchLocalization = manifest.Localizations.at(0);
+ REQUIRE(frenchLocalization.Locale == "fr-Fr");
+ REQUIRE(frenchLocalization.Get<Localization::Publisher>() == "Foo French");
+ REQUIRE(frenchLocalization.Get<Localization::PublisherUrl>() == "http://publisher-fr.net");
+ REQUIRE(frenchLocalization.Get<Localization::PublisherSupportUrl>() == "http://publisherSupport-fr.net");
+ REQUIRE(frenchLocalization.Get<Localization::PrivacyUrl>() == "http://packagePrivacyUrl-fr.net");
+ REQUIRE(frenchLocalization.Get<Localization::Author>() == "FooBar French");
+ REQUIRE(frenchLocalization.Get<Localization::PackageName>() == "Bar");
+ REQUIRE(frenchLocalization.Get<Localization::PackageUrl>() == "http://packageUrl-fr.net");
+ REQUIRE(frenchLocalization.Get<Localization::License>() == "Foo Bar License");
+ REQUIRE(frenchLocalization.Get<Localization::LicenseUrl>() == "http://licenseUrl-fr.net");
+ REQUIRE(frenchLocalization.Get<Localization::Copyright>() == "Foo Bar Copyright");
+ REQUIRE(frenchLocalization.Get<Localization::CopyrightUrl>() == "http://copyrightUrl-fr.net");
+ REQUIRE(frenchLocalization.Get<Localization::ShortDescription>() == "Foo bar is a foo bar French.");
+ REQUIRE(frenchLocalization.Get<Localization::Description>() == "Foo bar is a placeholder French.");
+ REQUIRE(frenchLocalization.Get<Localization::Tags>().size() == 3);
+ REQUIRE(frenchLocalization.Get<Localization::Tags>().at(0) == "FooBarFr");
+ REQUIRE(frenchLocalization.Get<Localization::Tags>().at(1) == "FooFr");
+ REQUIRE(frenchLocalization.Get<Localization::Tags>().at(2) == "BarFr");
+ REQUIRE(frenchLocalization.Get<Localization::ReleaseNotes>() == "Release notes");
+ REQUIRE(frenchLocalization.Get<Localization::ReleaseNotesUrl>() == "https://ReleaseNotes.net");
+ REQUIRE(frenchLocalization.Get<Localization::Agreements>().size() == 1);
+ REQUIRE(frenchLocalization.Get<Localization::Agreements>().at(0).Label == "Label");
+ REQUIRE(frenchLocalization.Get<Localization::Agreements>().at(0).AgreementText == "Text");
+ REQUIRE(frenchLocalization.Get<Localization::Agreements>().at(0).AgreementUrl == "https://AgreementUrl.net");
+ REQUIRE(frenchLocalization.Get<Localization::PurchaseUrl>() == "http://purchaseUrl.net");
+ REQUIRE(frenchLocalization.Get<Localization::InstallationNotes>() == "Installation Notes");
+ REQUIRE(frenchLocalization.Get<Localization::Documentations>().size() == 1);
+ REQUIRE(frenchLocalization.Get<Localization::Documentations>().at(0).DocumentLabel == "Document Label");
+ REQUIRE(frenchLocalization.Get<Localization::Documentations>().at(0).DocumentUrl == "http://documentUrl.net");
+ REQUIRE(frenchLocalization.Get<Localization::Icons>().size() == 1);
+ REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).Url == "https://testIcon");
+ REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).FileType == IconFileTypeEnum::Png);
+ REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).Resolution == IconResolutionEnum::Square32);
+ REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).Theme == IconThemeEnum::Light);
+ REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321"));
+ }
+
+ void VerifyInstallers_AllFields(const AppInstaller::Manifest::Manifest& manifest)
+ {
+ REQUIRE(manifest.Installers.size() == 1);
+
+ ManifestInstaller actualInstaller = manifest.Installers.at(0);
+ REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6"));
+ REQUIRE(actualInstaller.Url == "http://foobar.zip");
+ REQUIRE(actualInstaller.Arch == Architecture::X86);
+ REQUIRE(actualInstaller.Locale == "en-US");
+ REQUIRE(actualInstaller.Platform.size() == 1);
+ REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop);
+ REQUIRE(actualInstaller.MinOSVersion == "1078");
+ REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip);
+ REQUIRE(actualInstaller.Scope == ScopeEnum::User);
+ REQUIRE(actualInstaller.InstallModes.size() == 1);
+ REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive);
+ REQUIRE(actualInstaller.Switches.size() == 8);
+ REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s");
+ REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s");
+ REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i");
+ REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1");
+ REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l");
+ REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u");
+ REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom");
+ REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Repair) == "/repair");
+ REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1);
+ REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0);
+ REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Deny);
+ REQUIRE(actualInstaller.Commands.at(0) == "command1");
+ REQUIRE(actualInstaller.Protocols.at(0) == "protocol1");
+ REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension");
+ REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1"));
+ REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1"));
+ REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0"));
+ REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz"));
+ REQUIRE(actualInstaller.PackageFamilyName == "");
+ REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d");
+ REQUIRE(actualInstaller.ReleaseDate == "2021-01-01");
+ REQUIRE(actualInstaller.InstallerAbortsTerminal);
+ REQUIRE(actualInstaller.InstallLocationRequired);
+ REQUIRE(actualInstaller.RequireExplicitUpgrade);
+ REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf);
+ REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1);
+ REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm);
+ REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1);
+ REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName");
+ REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion");
+ REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher");
+ REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode");
+ REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode");
+ REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe);
+ REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1);
+ REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US");
+ REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom);
+ REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net");
+ REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable);
+ REQUIRE(actualInstaller.DisplayInstallWarnings);
+ REQUIRE(actualInstaller.UnsupportedArguments.size() == 1);
+ REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log);
+ REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1);
+ REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe");
+ REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe");
+ REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation");
+ REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1);
+ REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe");
+ REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch);
+ REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6"));
+ REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter");
+ REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test");
+ REQUIRE(actualInstaller.DownloadCommandProhibited);
+ REQUIRE(actualInstaller.RepairBehavior == RepairBehaviorEnum::Uninstaller);
+ }
+ };
}
TEST_CASE("GetManifests_GoodRequest_Authentication", "[RestSource][Interface_1_7]")
@@ -228,4 +583,24 @@ TEST_CASE("Search_BadRequest_InvalidAuthenticationToken", "[RestSource][Interfac
PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "package" };
request.Filters.emplace_back(std::move(filter));
REQUIRE_THROWS_HR(v1_7.Search(request), HTTP_E_STATUS_DENIED);
+}
+
+TEST_CASE("GetManifests_GoodResponse_V1_7", "[RestSource][Interface_1_7]")
+{
+ GoodManifest_AllFields sampleManifest;
+ utility::string_t sample = sampleManifest.GetSampleManifest_AllFields();
+ HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) };
+ Interface v1_7{ TestRestUriString, {}, {}, {}, std::move(helper) };
+ std::vector<AppInstaller::Manifest::Manifest> manifests = v1_7.GetManifests("Foo.Bar");
+ REQUIRE(manifests.size() == 1);
+
+ // Verify manifest is populated
+ AppInstaller::Manifest::Manifest& manifest = manifests[0];
+ REQUIRE(manifest.Id == "Foo.Bar");
+ REQUIRE(manifest.Version == "3.0.0abc");
+ REQUIRE(manifest.Moniker == "FooBarMoniker");
+ REQUIRE(manifest.Channel == "");
+ REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.7.0" });
+ sampleManifest.VerifyLocalizations_AllFields(manifest);
+ sampleManifest.VerifyInstallers_AllFields(manifest);
}
\ No newline at end of file
diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj
@@ -432,6 +432,7 @@
<ClInclude Include="Rest\Schema\1_6\Interface.h" />
<ClInclude Include="Rest\Schema\1_6\Json\ManifestDeserializer.h" />
<ClInclude Include="Rest\Schema\1_7\Interface.h" />
+ <ClInclude Include="Rest\Schema\1_7\Json\ManifestDeserializer.h" />
<ClInclude Include="Rest\Schema\AuthenticationInfoParser.h" />
<ClInclude Include="Rest\Schema\CommonRestConstants.h" />
<ClInclude Include="Rest\Schema\HttpClientHelper.h" />
@@ -515,6 +516,7 @@
<ClCompile Include="Rest\Schema\1_5\RestInterface_1_5.cpp" />
<ClCompile Include="Rest\Schema\1_6\Json\ManifestDeserializer_1_6.cpp" />
<ClCompile Include="Rest\Schema\1_6\RestInterface_1_6.cpp" />
+ <ClCompile Include="Rest\Schema\1_7\Json\ManifestDeserializer_1_7.cpp" />
<ClCompile Include="Rest\Schema\1_7\RestInterface_1_7.cpp" />
<ClCompile Include="Rest\Schema\AuthenticationInfoParser.cpp" />
<ClCompile Include="Rest\Schema\HttpClientHelper.cpp" />
diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters
@@ -97,6 +97,9 @@
<Filter Include="Rest\Schema\1_7">
<UniqueIdentifier>{f42acfce-4fc0-435b-a9a2-bf5528aecbcc}</UniqueIdentifier>
</Filter>
+ <Filter Include="Rest\Schema\1_7\Json">
+ <UniqueIdentifier>{7fd6c265-81c0-4c6e-87b2-24bef117e21d}</UniqueIdentifier>
+ </Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
@@ -402,6 +405,9 @@
<ClInclude Include="Rest\Schema\AuthenticationInfoParser.h">
<Filter>Rest\Schema</Filter>
</ClInclude>
+ <ClInclude Include="Rest\Schema\1_7\Json\ManifestDeserializer.h">
+ <Filter>Rest\Schema\1_7\Json</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
@@ -632,6 +638,9 @@
<ClCompile Include="Rest\Schema\AuthenticationInfoParser.cpp">
<Filter>Rest\Schema</Filter>
</ClCompile>
+ <ClCompile Include="Rest\Schema\1_7\Json\ManifestDeserializer_1_7.cpp">
+ <Filter>Rest\Schema\1_7\Json</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
diff --git a/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp b/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp
@@ -7,6 +7,7 @@
#include "Rest/Schema/1_4/Json/ManifestDeserializer.h"
#include "Rest/Schema/1_5/Json/ManifestDeserializer.h"
#include "Rest/Schema/1_6/Json/ManifestDeserializer.h"
+#include "Rest/Schema/1_7/Json/ManifestDeserializer.h"
namespace AppInstaller::Repository::JSON
{
@@ -37,14 +38,18 @@ namespace AppInstaller::Repository::JSON
{
m_pImpl->m_deserializer = std::make_unique<Rest::Schema::V1_4::Json::ManifestDeserializer>();
}
- else if (parts.size() > 1 && parts[1].Integer < 6)
+ else if (parts.size() > 1 && parts[1].Integer < 6)
{
m_pImpl->m_deserializer = std::make_unique<Rest::Schema::V1_5::Json::ManifestDeserializer>();
}
- else
+ else if (parts.size() > 1 && parts[1].Integer < 7)
{
m_pImpl->m_deserializer = std::make_unique<Rest::Schema::V1_6::Json::ManifestDeserializer>();
}
+ else
+ {
+ m_pImpl->m_deserializer = std::make_unique<Rest::Schema::V1_7::Json::ManifestDeserializer>();
+ }
}
else
{
diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h
@@ -40,6 +40,8 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json
virtual std::optional<Manifest::ManifestInstaller> DeserializeInstaller(const web::json::value& installerJsonObject) const;
+ virtual std::map<Manifest::InstallerSwitchType, Manifest::string_t> DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const;
+
std::optional<Manifest::DependencyList> DeserializeDependency(const web::json::value& dependenciesJsonObject) const;
virtual Manifest::InstallerTypeEnum ConvertToInstallerType(std::string_view in) const;
diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer_1_0.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer_1_0.cpp
@@ -219,6 +219,16 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json
std::optional<Manifest::ManifestInstaller> installerObject = DeserializeInstaller(installer);
if (installerObject)
{
+ // Merge default switches after parsing.
+ auto defaultSwitches = Manifest::GetDefaultKnownSwitches(installerObject->EffectiveInstallerType());
+ for (auto const& defaultSwitch : defaultSwitches)
+ {
+ if (installerObject->Switches.find(defaultSwitch.first) == installerObject->Switches.end())
+ {
+ installerObject->Switches[defaultSwitch.first] = defaultSwitch.second;
+ }
+ }
+
manifest.Installers.emplace_back(std::move(installerObject.value()));
}
}
@@ -374,19 +384,11 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json
}
// Installer Switches
- installer.Switches = Manifest::GetDefaultKnownSwitches(installer.EffectiveInstallerType());
std::optional<std::reference_wrapper<const web::json::value>> switches =
JSON::GetJsonValueFromNode(installerJsonObject, JSON::GetUtilityString(InstallerSwitches));
if (switches)
{
- const auto& installerSwitches = switches.value().get();
- TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::Silent, installerSwitches, Silent);
- TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::SilentWithProgress, installerSwitches, SilentWithProgress);
- TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::Interactive, installerSwitches, Interactive);
- TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::InstallLocation, installerSwitches, InstallLocation);
- TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::Log, installerSwitches, Log);
- TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::Update, installerSwitches, Upgrade);
- TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::Custom, installerSwitches, Custom);
+ installer.Switches = DeserializeInstallerSwitches(switches.value().get());
}
// Installer SuccessCodes
@@ -433,6 +435,21 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json
return installer;
}
+ std::map<Manifest::InstallerSwitchType, Manifest::string_t> ManifestDeserializer::DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const
+ {
+ std::map<Manifest::InstallerSwitchType, Manifest::string_t> installerSwitches;
+
+ TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Silent, installerSwitchesJsonObject, Silent);
+ TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::SilentWithProgress, installerSwitchesJsonObject, SilentWithProgress);
+ TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Interactive, installerSwitchesJsonObject, Interactive);
+ TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::InstallLocation, installerSwitchesJsonObject, InstallLocation);
+ TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Log, installerSwitchesJsonObject, Log);
+ TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Update, installerSwitchesJsonObject, Upgrade);
+ TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Custom, installerSwitchesJsonObject, Custom);
+
+ return installerSwitches;
+ }
+
std::optional<Manifest::DependencyList> ManifestDeserializer::DeserializeDependency(const web::json::value& dependenciesObject) const
{
if (dependenciesObject.is_null())
diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer.h
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#pragma once
+#include "Rest/Schema/1_6/Json/ManifestDeserializer.h"
+
+namespace AppInstaller::Repository::Rest::Schema::V1_7::Json
+{
+ // Manifest Deserializer.
+ struct ManifestDeserializer : public V1_6::Json::ManifestDeserializer
+ {
+ protected:
+
+ std::optional<Manifest::ManifestInstaller> DeserializeInstaller(const web::json::value& installerJsonObject) const override;
+
+ std::map<Manifest::InstallerSwitchType, Manifest::string_t> DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const override;
+
+ Manifest::ManifestVer GetManifestVersion() const override;
+ };
+}
diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer_1_7.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer_1_7.cpp
@@ -0,0 +1,50 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#include "pch.h"
+#include "ManifestDeserializer.h"
+#include <winget/JsonUtil.h>
+
+using namespace AppInstaller::Manifest;
+
+namespace AppInstaller::Repository::Rest::Schema::V1_7::Json
+{
+ namespace
+ {
+ // Installer
+ constexpr std::string_view InstallerSwitchRepair = "Repair"sv;
+ constexpr std::string_view RepairBehavior = "RepairBehavior"sv;
+ }
+
+ std::optional<Manifest::ManifestInstaller> ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const
+ {
+ auto result = V1_6::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject);
+
+ if (result)
+ {
+ auto& installer = result.value();
+
+ installer.RepairBehavior = Manifest::ConvertToRepairBehaviorEnum(JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(RepairBehavior)).value_or(""));
+ }
+
+ return result;
+ }
+
+ std::map<Manifest::InstallerSwitchType, Manifest::string_t> ManifestDeserializer::DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const
+ {
+ auto installerSwitches = V1_6::Json::ManifestDeserializer::DeserializeInstallerSwitches(installerSwitchesJsonObject);
+
+ auto repairValue = JSON::GetRawStringValueFromJsonNode(installerSwitchesJsonObject, JSON::GetUtilityString(InstallerSwitchRepair));
+
+ if (JSON::IsValidNonEmptyStringValue(repairValue))
+ {
+ installerSwitches[Manifest::InstallerSwitchType::Repair] = repairValue.value();
+ }
+
+ return installerSwitches;
+ }
+
+ Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const
+ {
+ return Manifest::s_ManifestVersionV1_7;
+ }
+}