diff --git a/doc/update.md b/doc/update.md index 4c05beb6..3609c50c 100644 --- a/doc/update.md +++ b/doc/update.md @@ -32,6 +32,9 @@ Override the architecture of an installer: `wingetcreate.exe update --urls | --version ` +Override the scope of an installer: +`wingetcreate.exe update --urls | --version ` + Update an existing manifest and submit PR to GitHub: `wingetcreate.exe update --submit --token --urls --version ` diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs index 9b38cfc4..aed95c05 100644 --- a/src/WingetCreateCLI/Commands/BaseCommand.cs +++ b/src/WingetCreateCLI/Commands/BaseCommand.cs @@ -224,9 +224,9 @@ protected static void DisplayArchitectureWarnings(List instal i.UrlArchitecture != i.BinaryArchitecture); if (mismatchedArchInstallers.Any()) - { - Logger.WarnLocalized(nameof(Resources.DetectedArchMismatch_Message)); + { Console.WriteLine(); + Logger.WarnLocalized(nameof(Resources.DetectedArchMismatch_Message)); foreach (var mismatch in mismatchedArchInstallers) { Logger.WarnLocalized(nameof(Resources.InstallerBinaryMismatch_Message), mismatch.UrlArchitecture, mismatch.BinaryArchitecture); diff --git a/src/WingetCreateCLI/Commands/UpdateCommand.cs b/src/WingetCreateCLI/Commands/UpdateCommand.cs index f0b4bbb7..da8644ce 100644 --- a/src/WingetCreateCLI/Commands/UpdateCommand.cs +++ b/src/WingetCreateCLI/Commands/UpdateCommand.cs @@ -43,6 +43,7 @@ public static IEnumerable Examples yield return new Example(Resources.Example_UpdateCommand_SearchAndUpdateVersionAndInstallerURL, new UpdateCommand { Id = "", InstallerUrls = new string[] { "", "" }, Version = "" }); yield return new Example(Resources.Example_UpdateCommand_SaveAndPublish, new UpdateCommand { Id = "", Version = "", OutputDir = "", GitHubToken = "" }); yield return new Example(Resources.Example_UpdateCommand_OverrideArchitecture, new UpdateCommand { Id = "", InstallerUrls = new string[] { "|" }, Version = "" }); + yield return new Example(Resources.Example_UpdateCommand_OverrideScope, new UpdateCommand { Id = "", InstallerUrls = new string[] { "|" }, Version = "" }); yield return new Example(Resources.Example_UpdateCommand_SubmitToGitHub, new UpdateCommand { Id = "", Version = "", InstallerUrls = new string[] { "", "" }, SubmitToGitHub = true, GitHubToken = "" }); } } @@ -231,16 +232,16 @@ public async Task UpdateManifestsAutonomously(Manifests manifests) this.InstallerUrls = installerManifest.Installers.Select(i => i.InstallerUrl).Distinct().ToArray(); } - // Generate list of InstallerUpdate objects and parse out any specified architecture overrides. - List installerMetadataList = this.ParseInstallerUrlsForArchOverride(this.InstallerUrls.Select(i => i.Trim()).ToList()); + // Generate list of InstallerUpdate objects and parse out any specified architecture or scope overrides. + List installerMetadataList = this.ParseInstallerUrlsForOverrides(this.InstallerUrls.Select(i => i.Trim()).ToList()); - // If the installer update list is null there was an issue when parsing for architecture override. + // If the installer update list is null there was an issue when parsing for architecture or scope override. if (installerMetadataList == null) { return null; } - // Reassign list with parsed installer URLs without architecture overrides. + // Reassign list with parsed installer URLs without architecture or scope overrides. this.InstallerUrls = installerMetadataList.Select(x => x.InstallerUrl).ToList(); foreach (var installerUpdate in installerMetadataList) @@ -249,6 +250,11 @@ public async Task UpdateManifestsAutonomously(Manifests manifests) { Logger.WarnLocalized(nameof(Resources.OverridingArchitecture_Warning), installerUpdate.InstallerUrl, installerUpdate.OverrideArchitecture); } + + if (installerUpdate.OverrideScope.HasValue) + { + Logger.WarnLocalized(nameof(Resources.OverridingScope_Warning), installerUpdate.InstallerUrl, installerUpdate.OverrideScope); + } } // We only support updates with same number of installer URLs @@ -561,11 +567,11 @@ private static void DisplayManifestsAsMenuSelection(Manifests manifests) } /// - /// Parse out architecture overrides included in the installer URLs and returns the parsed list of installer URLs. + /// Parses the installer urls for any architecture or scope overrides. /// /// List of installer URLs to be parsed for architecture overrides. /// List of helper objects used for updating the installers. - private List ParseInstallerUrlsForArchOverride(List installerUrlsToBeParsed) + private List ParseInstallerUrlsForOverrides(List installerUrlsToBeParsed) { List installerMetadataList = new List(); foreach (string item in installerUrlsToBeParsed) @@ -577,24 +583,55 @@ private List ParseInstallerUrlsForArchOverride(List i // '|' character indicates that an architecture override can be parsed from the installer. string[] installerUrlOverride = item.Split('|'); - if (installerUrlOverride.Length > 2) + // There can be at most 3 elements at one time (installerUrl|archOverride|scopeOverride) + if (installerUrlOverride.Length > 3) { - Logger.ErrorLocalized(nameof(Resources.MultipleArchitectureOverride_Error)); + Logger.ErrorLocalized(nameof(Resources.OverrideLimitExceeded_Error), item); return null; } - string installerUrl = installerUrlOverride[0]; - string overrideArchString = installerUrlOverride[1]; - Architecture? overrideArch = overrideArchString.ToEnumOrDefault(); - if (overrideArch.HasValue) - { - installerMetadata.InstallerUrl = installerUrl; - installerMetadata.OverrideArchitecture = overrideArch.Value; - } - else + installerMetadata.InstallerUrl = installerUrlOverride[0]; + + bool archOverridePresent = false; + bool scopeOverridePresent = false; + + for (int i = 1; i < installerUrlOverride.Length; i++) { - Logger.ErrorLocalized(nameof(Resources.UnableToParseArchOverride_Error), overrideArchString); - return null; + string overrideString = installerUrlOverride[i]; + Architecture? overrideArch = overrideString.ToEnumOrDefault(); + Scope? overrideScope = overrideString.ToEnumOrDefault(); + + if (overrideArch.HasValue) + { + if (archOverridePresent) + { + Logger.ErrorLocalized(nameof(Resources.MultipleArchitectureOverride_Error)); + return null; + } + else + { + archOverridePresent = true; + installerMetadata.OverrideArchitecture = overrideArch.Value; + } + } + else if (overrideScope.HasValue) + { + if (scopeOverridePresent) + { + Logger.ErrorLocalized(nameof(Resources.MultipleScopeOverride_Error)); + return null; + } + else + { + scopeOverridePresent = true; + installerMetadata.OverrideScope = overrideScope.Value; + } + } + else + { + Logger.ErrorLocalized(nameof(Resources.UnableToParseOverride_Error), overrideString); + return null; + } } } else diff --git a/src/WingetCreateCLI/Properties/Resources.Designer.cs b/src/WingetCreateCLI/Properties/Resources.Designer.cs index eff1f0a1..13c42e75 100644 --- a/src/WingetCreateCLI/Properties/Resources.Designer.cs +++ b/src/WingetCreateCLI/Properties/Resources.Designer.cs @@ -205,7 +205,7 @@ public static string Architecture_KeywordDescription { } /// - /// Looks up a localized string similar to If the installer you provided fails to match an existing installer even when overriding the architecture, you may need to edit the existing manifest manually. Make sure that the existing manifest has a single installer that matches the overriding architecture and installer type of the new installer. To modify an existing manifest, use the '--interactive' flag with the update command and submit the new changes. Once the changes are published, please try again.. + /// Looks up a localized string similar to If the installer you provided fails to match an existing installer even when overriding the architecture, you may need to override the scope or edit the existing manifest manually. Make sure that the existing manifest has a single installer that matches the overriding architecture, scope and installer type of the new installer. To modify an existing manifest, use the '--interactive' flag with the update command and submit the new changes. Once the changes are published, please try again.. /// public static string ArchitectureOverride_Warning { get { @@ -412,20 +412,20 @@ public static string DefenderVirus_ErrorMessage { } /// - /// Looks up a localized string similar to Deleting {0}. + /// Looks up a localized string similar to Cached token was invalid, deleting token from cache.... /// - public static string DeletingItem_Message { + public static string DeletingInvalidCachedToken_Message { get { - return ResourceManager.GetString("DeletingItem_Message", resourceCulture); + return ResourceManager.GetString("DeletingInvalidCachedToken_Message", resourceCulture); } } /// - /// Looks up a localized string similar to Cached token was invalid, deleting token from cache.... + /// Looks up a localized string similar to Deleting {0}. /// - public static string DeletingInvalidCachedToken_Message { + public static string DeletingItem_Message { get { - return ResourceManager.GetString("DeletingInvalidCachedToken_Message", resourceCulture); + return ResourceManager.GetString("DeletingItem_Message", resourceCulture); } } @@ -455,18 +455,16 @@ public static string DetectedArchMismatch_Message { return ResourceManager.GetString("DetectedArchMismatch_Message", resourceCulture); } } - + /// /// Looks up a localized string similar to {0} directories found in {1}. /// - public static string DirectoriesFound_Message - { - get - { + public static string DirectoriesFound_Message { + get { return ResourceManager.GetString("DirectoriesFound_Message", resourceCulture); } } - + /// /// Looks up a localized string similar to Would you like to discard this update and start over?. /// @@ -719,6 +717,15 @@ public static string Example_UpdateCommand_OverrideArchitecture { } } + /// + /// Looks up a localized string similar to Override the scope of an installer. + /// + public static string Example_UpdateCommand_OverrideScope { + get { + return ResourceManager.GetString("Example_UpdateCommand_OverrideScope", resourceCulture); + } + } + /// /// Looks up a localized string similar to Save and publish updated manifest. /// @@ -738,16 +745,14 @@ public static string Example_UpdateCommand_SearchAndUpdateVersionAndInstallerURL } /// - /// Looks up a localized string similar to Update an existing manifest and submit PR to GitHub + /// Looks up a localized string similar to Update an existing manifest and submit PR to GitHub. /// - public static string Example_UpdateCommand_SubmitToGitHub - { - get - { + public static string Example_UpdateCommand_SubmitToGitHub { + get { return ResourceManager.GetString("Example_UpdateCommand_SubmitToGitHub", resourceCulture); } } - + /// /// Looks up a localized string similar to The excluded installer target markets. /// @@ -1450,6 +1455,15 @@ public static string MultipleNestedArchitectures_Message { } } + /// + /// Looks up a localized string similar to Multiple scopes detected. Only one scope can be specified for an override.. + /// + public static string MultipleScopeOverride_Error { + get { + return ResourceManager.GetString("MultipleScopeOverride_Error", resourceCulture); + } + } + /// /// Looks up a localized string similar to The package name |e.g. Visual Studio|. /// @@ -1621,6 +1635,15 @@ public static string OutputDirectory_HelpText { } } + /// + /// Looks up a localized string similar to Too many overrides specified for the following installer URL: {0}. + /// + public static string OverrideLimitExceeded_Error { + get { + return ResourceManager.GetString("OverrideLimitExceeded_Error", resourceCulture); + } + } + /// /// Looks up a localized string similar to Overriding {0} with architecture {1}. /// @@ -1630,6 +1653,15 @@ public static string OverridingArchitecture_Warning { } } + /// + /// Looks up a localized string similar to Overriding {0} with scope {1}. + /// + public static string OverridingScope_Warning { + get { + return ResourceManager.GetString("OverridingScope_Warning", resourceCulture); + } + } + /// /// Looks up a localized string similar to List of package dependencies from current source. /// @@ -1838,7 +1870,7 @@ public static string PublisherUrl_KeywordDescription { } /// - /// Looks up a localized string similar to The title of the pull request submitted to GitHub. Default is "{PackageId} version {Version}" + /// Looks up a localized string similar to The title of the pull request submitted to GitHub. Default is "{PackageId} version {Version}". /// public static string PullRequestTitle_HelpText { get { @@ -2288,11 +2320,11 @@ public static string TokenExpired_Message { } /// - /// Looks up a localized string similar to Unable to parse the specified override architecture {0}.. + /// Looks up a localized string similar to Unable to parse the specified override {0}.. /// - public static string UnableToParseArchOverride_Error { + public static string UnableToParseOverride_Error { get { - return ResourceManager.GetString("UnableToParseArchOverride_Error", resourceCulture); + return ResourceManager.GetString("UnableToParseOverride_Error", resourceCulture); } } diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx index fdf59d90..c75e4ac4 100644 --- a/src/WingetCreateCLI/Properties/Resources.resx +++ b/src/WingetCreateCLI/Properties/Resources.resx @@ -799,15 +799,15 @@ Unable to complete the download request as the connection has timed out. Please verify your installer URL and try again. - If the installer you provided fails to match an existing installer even when overriding the architecture, you may need to edit the existing manifest manually. Make sure that the existing manifest has a single installer that matches the overriding architecture and installer type of the new installer. To modify an existing manifest, use the '--interactive' flag with the update command and submit the new changes. Once the changes are published, please try again. + If the installer you provided fails to match an existing installer even when overriding the architecture, you may need to override the scope or edit the existing manifest manually. Make sure that the existing manifest has a single installer that matches the overriding architecture, scope and installer type of the new installer. To modify an existing manifest, use the '--interactive' flag with the update command and submit the new changes. Once the changes are published, please try again. '--interactive' refers to a flag that can be included with the command-line arguments for this tool. Multiple architectures detected. Only one architecture can be specified for an override. - - Unable to parse the specified override architecture {0}. - {0} - represents the override architecture that failed to parse. + + Unable to parse the specified override {0}. + {0} - represents the override string that failed to parse. Override the architecture of an installer @@ -989,4 +989,19 @@ Failed to connect to GitHub. Check your network connection. + + Multiple scopes detected. Only one scope can be specified for an override. + + + Too many overrides specified for the following installer URL: {0} + {0} - represents the installer URL argument that the user is providing. The installer URL can be modified by appending '|scope|architecture' to override the detected values for that particular installer. + + + Overriding {0} with scope {1} + {0} - represents the InstallerUrl +{1} - represents the overriding scope + + + Override the scope of an installer + \ No newline at end of file diff --git a/src/WingetCreateCore/Common/PackageParser.cs b/src/WingetCreateCore/Common/PackageParser.cs index 393a5a73..98b532bf 100644 --- a/src/WingetCreateCore/Common/PackageParser.cs +++ b/src/WingetCreateCore/Common/PackageParser.cs @@ -300,7 +300,7 @@ public static bool ParsePackageAndUpdateInstallerNode(Installer installer, strin { // For a single installer, detect the architecture. If no architecture is detected, default to architecture from existing manifest. newInstaller.Architecture = GetArchFromUrl(url) ?? GetMachineType(path)?.ToString().ToEnumOrDefault() ?? installer.Architecture; - } + } newInstaller.InstallerUrl = url; newInstaller.InstallerSha256 = GetFileHash(path); @@ -321,8 +321,9 @@ public static Installer CloneInstaller(Installer installer) /// /// Finds an existing installer that matches the new installer by checking the installerType and the following: - /// 1. Matching based on architecture specified as an override. - /// 2. Matching based on architecture detected from URL string or binary. + /// 1. Matching based on architecture specified as an override if present. + /// 2. Matching based on architecture detected from URL string if present. + /// 3. If no singular match is found based on architecture, use scope to narrow down the match results if a scope override is present. /// /// New installer to be matched. /// List of existing installers to be matched. @@ -350,45 +351,64 @@ private static Installer FindInstallerMatch( installerTypeMatches = existingInstallers.Where( i => IsCompatibleInstallerType(i.InstallerType ?? installerManifest.InstallerType, newInstaller.InstallerType)); } - - var overrideArchMatches = installerTypeMatches.Where(i => i.Architecture == installerMetadata.OverrideArchitecture); - if (overrideArchMatches.Count() == 1) - { - return overrideArchMatches.Single(); - } - - var binaryArchitecture = installerMetadata.BinaryArchitecture ?? newInstaller.Architecture; - - // Msixbundle installers can have multiple installers with different architectures - // For those installers, the binaryArchitecture will be detected as null, therefore we default to the architecture specified in the newInstaller object. - var binaryArchMatches = installerTypeMatches.Where(i => i.Architecture == binaryArchitecture); - var urlArchMatches = installerTypeMatches.Where(i => i.Architecture == installerMetadata.UrlArchitecture); - - int numOfBinaryArchMatches = binaryArchMatches.Count(); - int numOfUrlArchMatches = urlArchMatches.Count(); - - // Count > 1 indicates multiple matches were found. Count == 0 indicates no matches were found. - // Since url string matching isn't always reliable, failing to find a match is okay. - // We only want to show an error if a string match finds multiple matches. - // If url string matching fails to find a match, show all errors that may occur from ArchAndTypeMatches. - if (numOfUrlArchMatches > 1) - { - multipleMatchedInstallers.Add(newInstaller); - } - else if (numOfUrlArchMatches == 0) - { - if (numOfBinaryArchMatches == 0) - { - unmatchedInstallers.Add(newInstaller); - } - else if (numOfBinaryArchMatches > 1) + + // Match installers using the installer architecture with the following priority: OverrideArchitecture > UrlArchitecture > BinaryArchitecture. + IEnumerable architectureMatches; + if (installerMetadata.OverrideArchitecture.HasValue) + { + architectureMatches = installerTypeMatches.Where(i => i.Architecture == installerMetadata.OverrideArchitecture); + } + else + { + if (installerMetadata.UrlArchitecture.HasValue) { - multipleMatchedInstallers.Add(newInstaller); - } - } - - var matchingExistingInstaller = numOfUrlArchMatches == 1 ? urlArchMatches.Single() : numOfBinaryArchMatches == 1 ? binaryArchMatches.Single() : null; - return matchingExistingInstaller; + architectureMatches = installerTypeMatches.Where(i => i.Architecture == installerMetadata.UrlArchitecture); + } + else + { + var binaryArchitecture = installerMetadata.BinaryArchitecture ?? newInstaller.Architecture; + architectureMatches = installerTypeMatches.Where(i => i.Architecture == binaryArchitecture); + } + } + + int architectureMatchesCount = architectureMatches.Count(); + if (architectureMatchesCount == 1) + { + return architectureMatches.Single(); + } + else if (architectureMatchesCount == 0) + { + unmatchedInstallers.Add(newInstaller); + } + else + { + // If there are multiple architecture matches, use scope to further narrow down the matches (if present). + IEnumerable scopeMatches; + if (installerMetadata.OverrideScope.HasValue) + { + scopeMatches = architectureMatches.Where(i => i.Scope == installerMetadata.OverrideScope); + } + else + { + scopeMatches = architectureMatches; + } + + int scopeMatchesCount = scopeMatches.Count(); + if (scopeMatchesCount == 1) + { + return scopeMatches.Single(); + } + else if (scopeMatchesCount == 0) + { + unmatchedInstallers.Add(newInstaller); + } + else + { + multipleMatchedInstallers.Add(newInstaller); + } + } + + return null; } /// diff --git a/src/WingetCreateCore/Models/InstallerMetadata.cs b/src/WingetCreateCore/Models/InstallerMetadata.cs index d673c10f..42fc5711 100644 --- a/src/WingetCreateCore/Models/InstallerMetadata.cs +++ b/src/WingetCreateCore/Models/InstallerMetadata.cs @@ -41,6 +41,11 @@ public class InstallerMetadata /// public Architecture? OverrideArchitecture { get; set; } + /// + /// Gets or sets the scope specified as an override. + /// + public Scope? OverrideScope { get; set; } + /// /// Gets or sets a value indicating whether the installer came from a zip. /// diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.ArchAndScopeOverride.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.ArchAndScopeOverride.yaml new file mode 100644 index 00000000..d1ecd9b6 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.ArchAndScopeOverride.yaml @@ -0,0 +1,21 @@ +PackageIdentifier: TestPublisher.ArchAndScopeOverride +PackageVersion: 0.1.2 +PackageName: Arch and Scope override test +Publisher: Test publisher +License: MIT +ShortDescription: A manifest used to test the update command when overriding the architecture and scope of the provided installers. +InstallerLocale: en-US +Installers: + - Architecture: arm + InstallerUrl: https://fakedomain.com/WingetCreateTestExeInstaller.exe + Scope: user + InstallerType: exe + InstallerSha256: A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC + - Architecture: arm + InstallerUrl: https://fakedomain.com/WingetCreateTestExeInstaller.exe + Scope: machine + InstallerType: exe + InstallerSha256: A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC +PackageLocale: en-US +ManifestType: singleton +ManifestVersion: 1.4.0 \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MatchWithInstallerUrl.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MatchWithArchFromInstallerUrl.yaml similarity index 90% rename from src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MatchWithInstallerUrl.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MatchWithArchFromInstallerUrl.yaml index 56e2e9d7..727cce81 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MatchWithInstallerUrl.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MatchWithArchFromInstallerUrl.yaml @@ -1,6 +1,6 @@ -PackageIdentifier: TestPublisher.MatchWithInstallerUrl +PackageIdentifier: TestPublisher.MatchWithArchFromInstallerUrl PackageVersion: 0.1.2 -PackageName: Match With Installer Url +PackageName: Match With Arch From Installer Url Publisher: Test publisher License: MIT ShortDescription: A manifest used to test the update command by matching installers based on the URL. diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MultipleArchitectureOverride.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.ScopeOverride.yaml similarity index 64% rename from src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MultipleArchitectureOverride.yaml rename to src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.ScopeOverride.yaml index 3742011f..e3d8efc6 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MultipleArchitectureOverride.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.ScopeOverride.yaml @@ -1,19 +1,21 @@ -PackageIdentifier: TestPublisher.MultipleArchitectureOverride +PackageIdentifier: TestPublisher.ScopeOverride PackageVersion: 0.1.2 -PackageName: Multiple Architecture Override +PackageName: Scope override test Publisher: Test publisher License: MIT -ShortDescription: A manifest used to test the update command when overriding the architectures of multiple installers. +ShortDescription: A manifest used to test the update command when overriding the scope of the provided installers. InstallerLocale: en-US Installers: - Architecture: x64 InstallerUrl: https://fakedomain.com/WingetCreateTestExeInstaller.exe + Scope: user InstallerType: exe InstallerSha256: A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC - - Architecture: x86 - InstallerUrl: https://fakedomain.com/win32/WingetCreateTestExeInstaller.exe + - Architecture: x64 + InstallerUrl: https://fakedomain.com/WingetCreateTestExeInstaller.exe + Scope: machine InstallerType: exe InstallerSha256: A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC PackageLocale: en-US ManifestType: singleton -ManifestVersion: 1.0.0 \ No newline at end of file +ManifestVersion: 1.4.0 \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs index ef2bc3eb..d1f41c82 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs @@ -218,7 +218,7 @@ public async Task UpdateFailsWithUnmatchedPackages() /// /// A representing the asynchronous unit test. [Test] - public async Task UpdateBasedOnInstallerUrlMatch() + public async Task UpdateWithArchDetectedFromInstallerUrl() { var archs = new[] { "arm64", "arm", "win64", "win32" }; var expectedArchs = new[] @@ -231,7 +231,7 @@ public async Task UpdateBasedOnInstallerUrlMatch() TestUtils.InitializeMockDownloads(archs.Select(a => $"{a}/{TestConstants.TestMsiInstaller}").ToArray()); - (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.MatchWithInstallerUrl", null, this.tempPath, null); + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.MatchWithArchFromInstallerUrl", null, this.tempPath, null); var initialManifests = Serialization.DeserializeManifestContents(initialManifestContent); var initialInstaller = initialManifests.SingletonManifest.Installers.First(); var updatedManifests = await RunUpdateCommand(command, initialManifestContent); @@ -294,7 +294,7 @@ public async Task UpdateWithArchitectureOverrideFailsParsing() var failedUpdateManifests = await RunUpdateCommand(badCommand, manifests); Assert.IsNull(failedUpdateManifests, "Command should have failed due to invalid architecture specified for override."); string result = this.sw.ToString(); - Assert.That(result, Does.Contain(string.Format(Resources.UnableToParseArchOverride_Error, invalidArch)), "Failed to show architecture override parsing error."); + Assert.That(result, Does.Contain(string.Format(Resources.UnableToParseOverride_Error, invalidArch)), "Failed to show architecture override parsing error."); } /// @@ -313,6 +313,22 @@ public async Task UpdateFailsOverrideWithMultipleArchitectures() Assert.That(result, Does.Contain(Resources.MultipleArchitectureOverride_Error), "Failed to show multiple architecture overrides error."); } + /// + /// Verfies that an error message is shown if multiple architectures are specified for an override. + /// + /// A representing the result of the asynchronous operation. + [Test] + public async Task UpdateFailsOverrideWithMultipleScopes() + { + string installerUrl = $"https://fakedomain.com/{TestConstants.TestExeInstaller}"; + (UpdateCommand badCommand, var manifests) = + GetUpdateCommandAndManifestData("TestPublisher.ScopeOverride", "1.2.3.4", this.tempPath, new[] { $"{installerUrl}|user|machine" }); + var failedUpdateManifests = await RunUpdateCommand(badCommand, manifests); + Assert.IsNull(failedUpdateManifests, "Command should have failed due to multiple scope overrides specified for a single installer."); + string result = this.sw.ToString(); + Assert.That(result, Does.Contain(Resources.MultipleScopeOverride_Error), "Failed to show multiple scope overrides error."); + } + /// /// Verifies that the overriding architecture can be matched to the architecture specified in the existing manifest and the update succeeds. /// @@ -344,6 +360,82 @@ public async Task UpdateWithArchitectureOverrides() Assert.AreNotEqual(initialInstaller.InstallerSha256, updatedInstaller.InstallerSha256, "InstallerSha256 should be updated"); } + /// + /// Verifies that the overriding scope can be matched to the scope specified in the existing manifest and the update succeeds. + /// + /// A representing the result of the asynchronous operation. + [Test] + public async Task UpdateWithScopeOverrides() + { + TestUtils.InitializeMockDownload(); + TestUtils.SetMockHttpResponseContent(TestConstants.TestExeInstaller); + string testInstallerUrl = $"https://fakedomain.com/{TestConstants.TestExeInstaller}"; + + // Test without scope override should fail. + (UpdateCommand badCommand, var manifests) = + GetUpdateCommandAndManifestData("TestPublisher.ScopeOverride", "1.2.3.4", this.tempPath, new[] { testInstallerUrl, testInstallerUrl }); + var failedUpdateManifests = await RunUpdateCommand(badCommand, manifests); + Assert.IsNull(failedUpdateManifests, "Command should have failed without scope override as there are multiple installers with the same architecture."); + + // Test with scope override should pass. + (UpdateCommand goodCommand, var initialManifestContent) = + GetUpdateCommandAndManifestData("TestPublisher.ScopeOverride", "1.2.3.4", this.tempPath, new[] { $"{testInstallerUrl}|user", $"{testInstallerUrl}|machine" }); + var initialManifests = Serialization.DeserializeManifestContents(initialManifestContent); + var updatedManifests = await RunUpdateCommand(goodCommand, initialManifestContent); + Assert.IsNotNull(updatedManifests, "Command should have succeeded as installers should be overrided with scope."); + + var initialFirstInstaller = initialManifests.SingletonManifest.Installers[0]; + var initialSecondInstaller = initialManifests.SingletonManifest.Installers[1]; + + var updatedFirstInstaller = updatedManifests.InstallerManifest.Installers[0]; + var updatedSecondInstaller = updatedManifests.InstallerManifest.Installers[1]; + + Assert.AreEqual(Scope.User, updatedFirstInstaller.Scope, $"Scope should be preserved."); + Assert.AreEqual(Scope.Machine, updatedSecondInstaller.Scope, $"Scope should be preserved."); + + Assert.AreNotEqual(initialFirstInstaller.InstallerSha256, updatedFirstInstaller.InstallerSha256, "InstallerSha256 should be updated"); + Assert.AreNotEqual(initialSecondInstaller.InstallerSha256, updatedSecondInstaller.InstallerSha256, "InstallerSha256 should be updated"); + } + + /// + /// Verifies that the overriding both the architecture and scope is supported and the update succeeds. + /// + /// A representing the result of the asynchronous operation. + [Test] + public async Task UpdateWithArchAndScopeOverrides() + { + TestUtils.InitializeMockDownload(); + TestUtils.SetMockHttpResponseContent(TestConstants.TestExeInstaller); + string testInstallerUrl = $"https://fakedomain.com/{TestConstants.TestExeInstaller}"; + + // Test without architecture override should fail. + (UpdateCommand badCommand, var manifests) = + GetUpdateCommandAndManifestData("TestPublisher.ArchAndScopeOverride", "1.2.3.4", this.tempPath, new[] { testInstallerUrl, testInstallerUrl }); + var failedUpdateManifests = await RunUpdateCommand(badCommand, manifests); + Assert.IsNull(failedUpdateManifests, "Command should have failed without overrides"); + + // Test with scope and architecture override should pass. + (UpdateCommand goodCommand, var initialManifestContent) = + GetUpdateCommandAndManifestData("TestPublisher.ArchAndScopeOverride", "1.2.3.4", this.tempPath, new[] { $"{testInstallerUrl}|user|arm", $"{testInstallerUrl}|arm|machine" }); + var initialManifests = Serialization.DeserializeManifestContents(initialManifestContent); + var updatedManifests = await RunUpdateCommand(goodCommand, initialManifestContent); + Assert.IsNotNull(updatedManifests, "Command should have succeeded as installers should be overrided with architecture and scope."); + + var initialFirstInstaller = initialManifests.SingletonManifest.Installers[0]; + var initialSecondInstaller = initialManifests.SingletonManifest.Installers[1]; + + var updatedFirstInstaller = updatedManifests.InstallerManifest.Installers[0]; + var updatedSecondInstaller = updatedManifests.InstallerManifest.Installers[1]; + + Assert.AreEqual(Scope.User, updatedFirstInstaller.Scope, $"Scope should be preserved."); + Assert.AreEqual(Scope.Machine, updatedSecondInstaller.Scope, $"Scope should be preserved."); + Assert.AreEqual(Architecture.Arm, updatedFirstInstaller.Architecture, $"Architecture should be preserved."); + Assert.AreEqual(Architecture.Arm, updatedSecondInstaller.Architecture, $"Architecture should be preserved."); + + Assert.AreNotEqual(initialFirstInstaller.InstallerSha256, updatedFirstInstaller.InstallerSha256, "InstallerSha256 should be updated"); + Assert.AreNotEqual(initialSecondInstaller.InstallerSha256, updatedSecondInstaller.InstallerSha256, "InstallerSha256 should be updated"); + } + /// /// Verifies that using the same installerURL with multiple architecture overrides can successfully update multiple installers. /// @@ -392,35 +484,21 @@ public async Task UpdateWithArchitectureOverrideFailsWithErrorMessage() } /// - /// Test the architecture override with multiple installers. Verifies that if the override architecture does not match any installer, - /// the architecture detected from the url or the binary will be used instead to perform the matching. + /// Verifies that an error is shown if there are too many overrides specified for a given installer. /// /// A representing the result of the asynchronous operation. [Test] - public async Task UpdateWithMultipleArchitectureOverrides() + public async Task NumberOfOverridesExceeded() { - var expectedArchs = new[] - { - Architecture.Arm, - Architecture.X64, - }; - - string x64ExeInstallerUrl = $"https://fakedomain.com/{TestConstants.TestExeInstaller}"; - string x86ExeInstallerUrl = $"https://fakedomain.com/win32/{TestConstants.TestExeInstaller}"; - TestUtils.InitializeMockDownloads(TestConstants.TestExeInstaller, $"win32/{TestConstants.TestExeInstaller}"); + string installerUrl = $"https://fakedomain.com/{TestConstants.TestExeInstaller}"; + string installerUrlOverride = $"{installerUrl}|x64|user|test"; + TestUtils.InitializeMockDownloads(TestConstants.TestExeInstaller); (UpdateCommand command, var initialManifestContent) = - GetUpdateCommandAndManifestData("TestPublisher.MultipleArchitectureOverride", "1.2.3.4", this.tempPath, new[] { $"{x64ExeInstallerUrl}|arm", $"{x86ExeInstallerUrl}|x64" }); - - var initialManifests = Serialization.DeserializeManifestContents(initialManifestContent); - var initialInstaller = initialManifests.SingletonManifest.Installers.First(); + GetUpdateCommandAndManifestData("TestPublisher.ArchitectureOverride", "1.2.3.4", this.tempPath, new[] { installerUrlOverride }); var updatedManifests = await RunUpdateCommand(command, initialManifestContent); - Assert.IsNotNull(updatedManifests, "Command should have succeeded"); - - foreach (var item in expectedArchs.Zip(updatedManifests.InstallerManifest.Installers, (expectedArch, installer) => (expectedArch, installer))) - { - Assert.AreEqual(item.expectedArch, item.installer.Architecture, "Architecture override failed."); - Assert.AreNotEqual(initialInstaller.InstallerSha256, item.installer.InstallerSha256, "InstallerSha256 should be updated"); - } + Assert.IsNull(updatedManifests, "Command should have failed"); + string result = this.sw.ToString(); + Assert.That(result, Does.Contain(string.Format(Resources.OverrideLimitExceeded_Error, installerUrlOverride)), "Failed to show warning for override limit exceeded."); } /// diff --git a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj index 6650b2e8..4ffa02f1 100644 --- a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj +++ b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj @@ -36,6 +36,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -84,16 +90,13 @@ PreserveNewest - - PreserveNewest - PreserveNewest PreserveNewest - + PreserveNewest