From 4f44b226e17c0551b8cb10b1647a87d3f2416902 Mon Sep 17 00:00:00 2001 From: Muhammad Danish Date: Wed, 31 Jul 2024 00:20:35 +0500 Subject: [PATCH] Improve matching for ZIP with multiple nested installers (#529) --- src/WingetCreateCore/Common/PackageParser.cs | 22 ++++ .../TestPublisher.RetainInstallerFields.yaml | 13 +-- ...Publisher.ZipMultipleNestedInstallers.yaml | 61 +++++++++++ .../WingetCreateTests/TestConstants.cs | 5 + .../WingetCreateTests/TestUtils.cs | 75 +++++++++++++ .../UnitTests/UpdateCommandTests.cs | 103 +++++++++++++++++- .../WingetCreateTests.csproj | 11 +- 7 files changed, 273 insertions(+), 17 deletions(-) create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.ZipMultipleNestedInstallers.yaml diff --git a/src/WingetCreateCore/Common/PackageParser.cs b/src/WingetCreateCore/Common/PackageParser.cs index c56312ed..e0ab8823 100644 --- a/src/WingetCreateCore/Common/PackageParser.cs +++ b/src/WingetCreateCore/Common/PackageParser.cs @@ -216,6 +216,10 @@ public static void UpdateInstallerNodesAsync(List installerMe { parseFailedInstallerUrls.Add(installerMetadata.InstallerUrl); } + + // In case of multiple nested installers in the archive, we expect the new installers to have duplicates + // Remove these duplicates to avoid multiple matches. + installerMetadata.NewInstallers = installerMetadata.NewInstallers.Distinct().ToList(); } int numOfNewInstallers = installerMetadataList.Sum(x => x.NewInstallers.Count); @@ -262,6 +266,24 @@ public static void UpdateInstallerNodesAsync(List installerMe // If a match is found, add match to dictionary and remove for list of existingInstallers if (existingInstallerMatch != null) { + // Remove the nested installers from the new installer that are not present in the existing installer. + if (newInstaller.NestedInstallerFiles != null && existingInstallerMatch.NestedInstallerFiles != null) + { + var matchedFiles = newInstaller.NestedInstallerFiles + .Where(nif => + { + var fileName = Path.GetFileName(nif.RelativeFilePath); + + // If the flow reaches here, there's guaranteed to be a matching file name + // Any mismatches would've been detected earlier in the update flow. + return existingInstallerMatch.NestedInstallerFiles.Any(eif => + Path.GetFileName(eif.RelativeFilePath) == fileName); + }) + .ToList(); + + newInstaller.NestedInstallerFiles = matchedFiles; + } + installerMatchDict.Add(existingInstallerMatch, newInstaller); existingInstallers.Remove(existingInstallerMatch); } diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.RetainInstallerFields.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.RetainInstallerFields.yaml index ce0587b3..d384a7fd 100644 --- a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.RetainInstallerFields.yaml +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.RetainInstallerFields.yaml @@ -9,16 +9,13 @@ Description: |- 1) For the first installer, all root level fields are copied over and root fields are set to null. 2) For the second installer, installer level fields are preserved since they are not null. - 3) InstallerType and NestedInstallerType are common across both installers, so they are moved to the root level at the end of the update. - - TODO: Use different NestedInstallerType and RelativeFilePath for each installer once logic for handling multiple nested installers is improved. - Reference: https://github.com/microsoft/winget-create/issues/392 + 3) InstallerType is common across both installers, so it is moved to the root level at the end of the update. InstallerLocale: en-US InstallerType: zip NestedInstallerType: exe NestedInstallerFiles: - RelativeFilePath: WingetCreateTestExeInstaller.exe - PortableCommandAlias: TestAlias + PortableCommandAlias: TestExeAlias AppsAndFeaturesEntries: - DisplayName: TestDisplayName1 Publisher: TestPublisher1 @@ -87,10 +84,10 @@ Installers: InstallerType: zip InstallerUrl: https://fakedomain.com/WingetCreateTestZipInstaller.zip InstallerSha256: 8A052767127A6E2058BAAE03B551A807777BB1B726650E2C7E92C3E92C8DF80D - NestedInstallerType: exe + NestedInstallerType: msi NestedInstallerFiles: - - RelativeFilePath: WingetCreateTestExeInstaller.exe - PortableCommandAlias: TestAlias + - RelativeFilePath: WingetCreateTestMsiInstaller.msi + PortableCommandAlias: TestMsiAlias AppsAndFeaturesEntries: - DisplayName: TestDisplayName2 Publisher: TestPublisher2 diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.ZipMultipleNestedInstallers.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.ZipMultipleNestedInstallers.yaml new file mode 100644 index 00000000..d53ca8f3 --- /dev/null +++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.ZipMultipleNestedInstallers.yaml @@ -0,0 +1,61 @@ +PackageIdentifier: TestPublisher.MultipleNestedInstallers +PackageVersion: 0.1.2 +PackageName: Test zip app +Publisher: Test publisher +License: MIT +ShortDescription: A manifest used for testing whether multiple nested installers are handled correctly. +Description: The zip archive contains extra nested installers that are intentionally left out in the manifest to test the behavior. +Installers: +- Architecture: x64 + InstallerType: zip + InstallerUrl: https://fakedomain.com/WingetCreateTestMultipleNestedInstallers.zip + InstallerSha256: 8A052767127A6E2058BAAE03B551A807777BB1B726650E2C7E92C3E92C8DF80D + NestedInstallerType: portable + NestedInstallerFiles: + - RelativeFilePath: WingetCreateTestExeInstaller.exe + PortableCommandAlias: TestExeInstallerAlias + - RelativeFilePath: WingetCreateTestPortableInstaller.exe + PortableCommandAlias: TestPortableInstallerAlias + - RelativeFilePath: WingetCreateTestPortableInstaller (1).exe + PortableCommandAlias: TestPortableInstallerAlias1 + - RelativeFilePath: WingetCreateTestPortableInstaller (2).exe + PortableCommandAlias: TestPortableInstallerAlias2 +- Architecture: x86 + InstallerType: zip + InstallerUrl: https://fakedomain.com/WingetCreateTestMultipleNestedInstallers.zip + InstallerSha256: 8A052767127A6E2058BAAE03B551A807777BB1B726650E2C7E92C3E92C8DF80D + NestedInstallerType: portable + NestedInstallerFiles: + - RelativeFilePath: WingetCreateTestPortableInstaller.exe + PortableCommandAlias: TestPortableInstallerAlias + - RelativeFilePath: WingetCreateTestPortableInstaller (1).exe + PortableCommandAlias: TestPortableInstallerAlias1 +- Architecture: arm + InstallerType: zip + InstallerUrl: https://fakedomain.com/WingetCreateTestMultipleNestedInstallers.zip + InstallerSha256: 8A052767127A6E2058BAAE03B551A807777BB1B726650E2C7E92C3E92C8DF80D + NestedInstallerType: portable + NestedInstallerFiles: + - RelativeFilePath: WingetCreateTestPortableInstaller (2).exe + PortableCommandAlias: TestPortableInstallerAlias2 +- Architecture: arm64 + InstallerType: zip + Scope: user + InstallerUrl: https://fakedomain.com/WingetCreateTestMultipleNestedInstallers.zip + InstallerSha256: 8A052767127A6E2058BAAE03B551A807777BB1B726650E2C7E92C3E92C8DF80D + NestedInstallerType: exe + NestedInstallerFiles: + - RelativeFilePath: WingetCreateTestExeInstaller.exe + PortableCommandAlias: TestExeInstallerAlias +- Architecture: arm64 + InstallerType: zip + Scope: machine + InstallerUrl: https://fakedomain.com/WingetCreateTestMultipleNestedInstallers.zip + InstallerSha256: 8A052767127A6E2058BAAE03B551A807777BB1B726650E2C7E92C3E92C8DF80D + NestedInstallerType: msi + NestedInstallerFiles: + - RelativeFilePath: WingetCreateTestMsiInstaller.msi + PortableCommandAlias: TestMsiInstallerAlias +PackageLocale: en-US +ManifestType: singleton +ManifestVersion: 1.6.0 \ No newline at end of file diff --git a/src/WingetCreateTests/WingetCreateTests/TestConstants.cs b/src/WingetCreateTests/WingetCreateTests/TestConstants.cs index da8419c0..bdfc6ff8 100644 --- a/src/WingetCreateTests/WingetCreateTests/TestConstants.cs +++ b/src/WingetCreateTests/WingetCreateTests/TestConstants.cs @@ -35,6 +35,11 @@ public static class TestConstants /// public const string TestExeInstaller = "WingetCreateTestExeInstaller.exe"; + /// + /// File name of the test portable installer. + /// + public const string TestPortableInstaller = "WingetCreateTestPortableInstaller.exe"; + /// /// File name of the test MSI installer. /// diff --git a/src/WingetCreateTests/WingetCreateTests/TestUtils.cs b/src/WingetCreateTests/WingetCreateTests/TestUtils.cs index 8ec99984..b503ec3a 100644 --- a/src/WingetCreateTests/WingetCreateTests/TestUtils.cs +++ b/src/WingetCreateTests/WingetCreateTests/TestUtils.cs @@ -6,6 +6,7 @@ namespace Microsoft.WingetCreateTests using System; using System.Collections.Generic; using System.IO; + using System.IO.Compression; using System.Linq; using System.Net; using System.Net.Http; @@ -138,5 +139,79 @@ public static string MockDownloadFile(string filename) string downloadedPath = PackageParser.DownloadFileAsync(url).Result; return downloadedPath; } + + /// + /// Creates copies of the specified resource file. If multiple copies are requested, the new files will be named with a numeric suffix. + /// + /// Name of the resource file to copy. + /// Number of copies to create. + /// Optional new name for the copied resource file. + /// List of paths to the newly created files. + public static List CreateResourceCopy(string resource, int numberOfCopies = 1, string newResourceName = null) + { + string originalResourcePath = GetTestFile(resource); + string newResourcePath = originalResourcePath; + if (!string.IsNullOrEmpty(newResourceName)) + { + newResourcePath = Path.Combine(Path.GetDirectoryName(originalResourcePath), newResourceName); + } + + List copyPaths = new(); + for (int i = 0; i < numberOfCopies; i++) + { + string copyPath = PackageParser.GetNumericFilename(newResourcePath); + File.Copy(originalResourcePath, copyPath); + copyPaths.Add(copyPath); + } + + return copyPaths; + } + + /// + /// Adds files to an existing test zip archive. + /// + /// Name of the zip resource file. + /// List of paths for files to be included in the zip archive. + public static void AddFilesToZip(string zipResourceName, List filePaths) + { + string zipPath = GetTestFile(zipResourceName); + using (ZipArchive zipArchive = ZipFile.Open(zipPath, ZipArchiveMode.Update)) + { + foreach (string file in filePaths) + { + var fileInfo = new FileInfo(file); + zipArchive.CreateEntryFromFile(fileInfo.FullName, fileInfo.Name); + } + } // The zipArchive is automatically closed and disposed here + } + + /// + /// Removes files from an existing test zip archive. + /// + /// Name of the zip resource file. + /// List of file names to be removed from the zip archive. + public static void RemoveFilesFromZip(string zipResourceName, List fileNames) + { + string zipPath = GetTestFile(zipResourceName); + using (ZipArchive zipArchive = ZipFile.Open(zipPath, ZipArchiveMode.Update)) + { + foreach (string fileName in fileNames) + { + zipArchive.GetEntry(fileName)?.Delete(); + } + } // ZipArchive is automatically closed and disposed here + } + + /// + /// Delete test resources from cache directory. + /// + /// Name of the test files to delete. + public static void DeleteCachedFiles(List testFileNames) + { + foreach (string fileName in testFileNames) + { + File.Delete(Path.Combine(PackageParser.InstallerDownloadPath, fileName)); + } + } } } diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs index eca41fc6..6f70e23a 100644 --- a/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs +++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs @@ -1149,6 +1149,96 @@ public async Task UpdateMultipleZipInstallers() ClassicAssert.IsTrue(initialThirdInstaller.InstallerSha256 != updatedThirdInstaller.InstallerSha256, "InstallerSha256 should be updated"); } + /// + /// Verifies that updating a zip package with multiple nested installer packages works as expected. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task UpdateZipWithMultipleNestedInstallers() + { + // Create copies of test exe installer to be used as portable installers + List portableFilePaths = TestUtils.CreateResourceCopy(TestConstants.TestExeInstaller, 4, TestConstants.TestPortableInstaller); + + // Add the generated portable installers to the test zip installer + TestUtils.AddFilesToZip(TestConstants.TestZipInstaller, portableFilePaths); + + // Delete cached zip installer from other test runs so that the modified zip installer is downloaded + TestUtils.DeleteCachedFiles(new List { TestConstants.TestZipInstaller }); + + TestUtils.InitializeMockDownloads(TestConstants.TestZipInstaller); + string installerUrl = $"https://fakedomain.com/{TestConstants.TestZipInstaller}"; + (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.ZipMultipleNestedInstallers", null, this.tempPath, new[] { $"{installerUrl}|x64", $"{installerUrl}|x86", $"{installerUrl}|arm", $"{installerUrl}|arm64|user", $"{installerUrl}|arm64|machine" }); + + var updatedManifests = await RunUpdateCommand(command, initialManifestContent); + + // Perform test clean up before any assertions + portableFilePaths.ForEach(File.Delete); + TestUtils.RemoveFilesFromZip(TestConstants.TestZipInstaller, portableFilePaths.Select(Path.GetFileName).ToList()); + + ClassicAssert.IsNotNull(updatedManifests, "Command should have succeeded"); + + var initialManifests = Serialization.DeserializeManifestContents(initialManifestContent); + var initialInstallers = initialManifests.SingletonManifest.Installers; + var initialFirstInstaller = initialInstallers[0]; + var initialSecondInstaller = initialInstallers[1]; + var initialThirdInstaller = initialInstallers[2]; + var initialFourthInstaller = initialInstallers[3]; + var initialFifthInstaller = initialInstallers[4]; + + var updatedInstallerManifest = updatedManifests.InstallerManifest; + var updatedFirstInstaller = updatedInstallerManifest.Installers[0]; + var updatedSecondInstaller = updatedInstallerManifest.Installers[1]; + var updatedThirdInstaller = updatedInstallerManifest.Installers[2]; + var updatedFourthInstaller = updatedInstallerManifest.Installers[3]; + var updatedFifthInstaller = updatedInstallerManifest.Installers[4]; + + ClassicAssert.IsTrue(updatedInstallerManifest.InstallerType == InstallerType.Zip, "InstallerType should at the root level should be ZIP"); + + ClassicAssert.AreEqual(NestedInstallerType.Portable, updatedFirstInstaller.NestedInstallerType, "Nested installer type should be portable"); + ClassicAssert.IsTrue(updatedFirstInstaller.NestedInstallerFiles.Count == 4, "NestedInstallerFiles list should contain four members"); + ClassicAssert.IsTrue(initialFirstInstaller.NestedInstallerFiles[0].RelativeFilePath == updatedFirstInstaller.NestedInstallerFiles[0].RelativeFilePath, "RelativeFilePath should be preserved."); + ClassicAssert.IsTrue(initialFirstInstaller.NestedInstallerFiles[0].PortableCommandAlias == updatedFirstInstaller.NestedInstallerFiles[0].PortableCommandAlias, "PortableCommandAlias should be preserved."); + ClassicAssert.IsTrue(initialFirstInstaller.NestedInstallerFiles[1].RelativeFilePath == updatedFirstInstaller.NestedInstallerFiles[1].RelativeFilePath, "RelativeFilePath should be preserved."); + ClassicAssert.IsTrue(initialFirstInstaller.NestedInstallerFiles[1].PortableCommandAlias == updatedFirstInstaller.NestedInstallerFiles[1].PortableCommandAlias, "PortableCommandAlias should be preserved."); + ClassicAssert.IsTrue(initialFirstInstaller.NestedInstallerFiles[2].RelativeFilePath == updatedFirstInstaller.NestedInstallerFiles[2].RelativeFilePath, "RelativeFilePath should be preserved."); + ClassicAssert.IsTrue(initialFirstInstaller.NestedInstallerFiles[2].PortableCommandAlias == updatedFirstInstaller.NestedInstallerFiles[2].PortableCommandAlias, "PortableCommandAlias should be preserved."); + ClassicAssert.IsTrue(initialFirstInstaller.NestedInstallerFiles[3].RelativeFilePath == updatedFirstInstaller.NestedInstallerFiles[3].RelativeFilePath, "RelativeFilePath should be preserved."); + ClassicAssert.IsTrue(initialFirstInstaller.NestedInstallerFiles[3].PortableCommandAlias == updatedFirstInstaller.NestedInstallerFiles[3].PortableCommandAlias, "PortableCommandAlias should be preserved."); + + // 2nd installer + ClassicAssert.AreEqual(NestedInstallerType.Portable, updatedSecondInstaller.NestedInstallerType, "Nested installer type should be portable"); + ClassicAssert.IsTrue(updatedSecondInstaller.NestedInstallerFiles.Count == 2, "NestedInstallerFiles list should contain two members"); + ClassicAssert.IsTrue(initialSecondInstaller.NestedInstallerFiles[0].RelativeFilePath == updatedSecondInstaller.NestedInstallerFiles[0].RelativeFilePath, "RelativeFilePath should be preserved."); + ClassicAssert.IsTrue(initialSecondInstaller.NestedInstallerFiles[0].PortableCommandAlias == updatedSecondInstaller.NestedInstallerFiles[0].PortableCommandAlias, "PortableCommandAlias should be preserved."); + ClassicAssert.IsTrue(initialSecondInstaller.NestedInstallerFiles[1].RelativeFilePath == updatedSecondInstaller.NestedInstallerFiles[1].RelativeFilePath, "RelativeFilePath should be preserved."); + ClassicAssert.IsTrue(initialSecondInstaller.NestedInstallerFiles[1].PortableCommandAlias == updatedSecondInstaller.NestedInstallerFiles[1].PortableCommandAlias, "PortableCommandAlias should be preserved."); + + // 3rd installer + ClassicAssert.AreEqual(NestedInstallerType.Portable, updatedThirdInstaller.NestedInstallerType, "Nested installer type should be portable"); + ClassicAssert.IsTrue(updatedThirdInstaller.NestedInstallerFiles.Count == 1, "NestedInstallerFiles list should contain only one member"); + ClassicAssert.IsTrue(initialThirdInstaller.NestedInstallerFiles[0].RelativeFilePath == updatedThirdInstaller.NestedInstallerFiles[0].RelativeFilePath, "RelativeFilePath should be preserved."); + ClassicAssert.IsTrue(initialThirdInstaller.NestedInstallerFiles[0].PortableCommandAlias == updatedThirdInstaller.NestedInstallerFiles[0].PortableCommandAlias, "PortableCommandAlias should be preserved."); + + // 4th installer + ClassicAssert.AreEqual(NestedInstallerType.Exe, updatedFourthInstaller.NestedInstallerType, "Nested installer type should be EXE"); + ClassicAssert.IsTrue(updatedFourthInstaller.NestedInstallerFiles.Count == 1, "NestedInstallerFiles list should contain only one member"); + ClassicAssert.IsTrue(initialFourthInstaller.NestedInstallerFiles[0].RelativeFilePath == updatedFourthInstaller.NestedInstallerFiles[0].RelativeFilePath, "RelativeFilePath should be preserved."); + ClassicAssert.IsTrue(initialFourthInstaller.NestedInstallerFiles[0].PortableCommandAlias == updatedFourthInstaller.NestedInstallerFiles[0].PortableCommandAlias, "PortableCommandAlias should be preserved."); + + // 5th installer + ClassicAssert.AreEqual(NestedInstallerType.Msi, updatedFifthInstaller.NestedInstallerType, "Nested installer type should be MSI"); + ClassicAssert.IsTrue(updatedFifthInstaller.NestedInstallerFiles.Count == 1, "NestedInstallerFiles list should contain only one member"); + ClassicAssert.IsTrue(initialFifthInstaller.NestedInstallerFiles[0].RelativeFilePath == updatedFifthInstaller.NestedInstallerFiles[0].RelativeFilePath, "RelativeFilePath should be preserved."); + ClassicAssert.IsTrue(initialFifthInstaller.NestedInstallerFiles[0].PortableCommandAlias == updatedFifthInstaller.NestedInstallerFiles[0].PortableCommandAlias, "PortableCommandAlias should be preserved."); + + // Hashes should be updated + ClassicAssert.IsTrue(initialFirstInstaller.InstallerSha256 != updatedFirstInstaller.InstallerSha256, "InstallerSha256 should be updated"); + ClassicAssert.IsTrue(initialSecondInstaller.InstallerSha256 != updatedSecondInstaller.InstallerSha256, "InstallerSha256 should be updated"); + ClassicAssert.IsTrue(initialThirdInstaller.InstallerSha256 != updatedThirdInstaller.InstallerSha256, "InstallerSha256 should be updated"); + ClassicAssert.IsTrue(initialFourthInstaller.InstallerSha256 != updatedFourthInstaller.InstallerSha256, "InstallerSha256 should be updated"); + ClassicAssert.IsTrue(initialFifthInstaller.InstallerSha256 != updatedFifthInstaller.InstallerSha256, "InstallerSha256 should be updated"); + } + /// /// Verifies that moving common installer fields to the root of the manifest works as expected. /// @@ -1396,8 +1486,6 @@ public async Task UpdateOverwritesNullInstallerFields() /// 1) For the first installer, all root level fields are copied over and root fields are set to null. /// 2) For the second installer, installer level fields are preserved since they are not null. /// 3) InstallerType, NestedInstallerType and NestedInstallerFiles are common across both installers, so they are moved to the root level at the end of the update. - /// TODO: Use different NestedInstallerType and RelativeFilePath for each installer once logic for handling multiple nested installers is improved. - /// Reference: https://github.com/microsoft/winget-create/issues/392. /// /// A representing the result of the asynchronous operation. [Test] @@ -1423,6 +1511,9 @@ public async Task UpdateRetainsNonNullInstallerFields() ClassicAssert.IsTrue(firstInstaller.InstallLocationRequired == true, "InstallLocation for the first installer should be copied over from root"); ClassicAssert.IsTrue(firstInstaller.RequireExplicitUpgrade == true, "RequireExplicitUpgrade for the first installer should be copied over from root"); ClassicAssert.IsTrue(firstInstaller.DisplayInstallWarnings == true, "DisplayInstallWarnings for the first installer should be copied over from root"); + ClassicAssert.IsNotNull(firstInstaller.NestedInstallerFiles, "NestedInstallerFiles for the first installer should not be null"); + ClassicAssert.IsTrue(firstInstaller.NestedInstallerFiles[0].RelativeFilePath == "WingetCreateTestExeInstaller.exe", "RelativeFilePath for the first installer should be copied over from root"); + ClassicAssert.IsTrue(firstInstaller.NestedInstallerFiles[0].PortableCommandAlias == "TestExeAlias", "PortableCommandAlias for the first installer should be copied over from root"); ClassicAssert.IsNotNull(firstInstaller.InstallerSwitches, "InstallerSwitches for the first installer should not be null"); ClassicAssert.IsTrue(firstInstaller.InstallerSwitches.Silent == "/silent1", "Silent installer switch for the first installer should be copied over from root"); ClassicAssert.IsNotNull(firstInstaller.Dependencies, "Dependencies for the first installer should not be null"); @@ -1461,6 +1552,9 @@ public async Task UpdateRetainsNonNullInstallerFields() ClassicAssert.IsTrue(secondInstaller.InstallLocationRequired == false, "InstallLocation for the second installer should be preserved"); ClassicAssert.IsTrue(secondInstaller.RequireExplicitUpgrade == false, "RequireExplicitUpgrade for the second installer should be preserved"); ClassicAssert.IsTrue(secondInstaller.DisplayInstallWarnings == false, "DisplayInstallWarnings for the second installer should be preserved"); + ClassicAssert.IsNotNull(secondInstaller.NestedInstallerFiles, "NestedInstallerFiles for the first installer should not be null"); + ClassicAssert.IsTrue(secondInstaller.NestedInstallerFiles[0].RelativeFilePath == "WingetCreateTestMsiInstaller.msi", "RelativeFilePath for the second installer should be copied over from root"); + ClassicAssert.IsTrue(secondInstaller.NestedInstallerFiles[0].PortableCommandAlias == "TestMsiAlias", "PortableCommandAlias for the second installer should be copied over from root"); ClassicAssert.IsNotNull(secondInstaller.InstallerSwitches, "InstallerSwitches for the second installer should not be null"); ClassicAssert.IsTrue(secondInstaller.InstallerSwitches.Silent == "/silent2", "Silent installer switch for the second installer should be preserved"); ClassicAssert.IsNotNull(secondInstaller.Dependencies, "Dependencies for the second installer should not be null"); @@ -1491,6 +1585,8 @@ public async Task UpdateRetainsNonNullInstallerFields() // Root fields should be null ClassicAssert.IsNull(updatedInstallerManifest.Scope, "Scope at the root level should be null"); + ClassicAssert.IsNull(updatedInstallerManifest.NestedInstallerFiles, "NestedInstallerFiles at the root level should be null"); + ClassicAssert.IsNull(updatedInstallerManifest.NestedInstallerType, "NestedInstallerType at the root level should be null"); ClassicAssert.IsNull(updatedInstallerManifest.MinimumOSVersion, "MinimumOSVersion at the root level should be null"); ClassicAssert.IsNull(updatedInstallerManifest.PackageFamilyName, "PackageFamilyName at the root level should be null"); ClassicAssert.IsNull(updatedInstallerManifest.UpgradeBehavior, "UpgradeBehavior at the root level should be null"); @@ -1517,9 +1613,6 @@ public async Task UpdateRetainsNonNullInstallerFields() // Fields that should be moved to root ClassicAssert.IsTrue(updatedInstallerManifest.InstallerType == InstallerType.Zip, "InstallerType at the root level should be ZIP"); - ClassicAssert.IsTrue(updatedInstallerManifest.NestedInstallerType == NestedInstallerType.Exe, "NestedInstallerType at the root level should be EXE"); - ClassicAssert.IsNotNull(updatedInstallerManifest.NestedInstallerFiles, "NestedInstallerFiles at the root level should not be null"); - ClassicAssert.IsTrue(updatedInstallerManifest.NestedInstallerFiles[0].PortableCommandAlias == "TestAlias", "PortableCommandAlias at the root level should be TestAlias"); } /// diff --git a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj index 5f9040d4..304a360e 100644 --- a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj +++ b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj @@ -51,6 +51,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -190,16 +193,16 @@ PreserveNewest - PreserveNewest + PreserveNewest - PreserveNewest + PreserveNewest - PreserveNewest + PreserveNewest - PreserveNewest + PreserveNewest