Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/WingetIntune/Models/Manifest/WingetInstallerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,24 @@ internal static class WingetInstallerExtensions
private static Winget.CommunityRepository.Models.WingetInstaller? singleOrDefault(this IList<Winget.CommunityRepository.Models.WingetInstaller> installers, InstallerType installerType, Architecture architecture, InstallerContext installerContext, string? locale = null)
{
return installers.FirstOrDefault(i =>
(i.ParseInstallerType() == installerType || installerType == InstallerType.Unknown)
InstallerTypeMatches(i, installerType)
&& (i.InstallerArchitecture() == architecture || (architecture == Architecture.Unknown && i.InstallerArchitecture() == Architecture.X64))
&& (i.ParseInstallerContext() == installerContext || installerContext == InstallerContext.Unknown)
&& (string.IsNullOrWhiteSpace(locale) || i.InstallerLocale == locale));
}

private static bool InstallerTypeMatches(Winget.CommunityRepository.Models.WingetInstaller installer, InstallerType requestedType)
{
if (requestedType == InstallerType.Unknown)
return true;
if (installer.ParseInstallerType() == requestedType)
return true;
// For zip installers, also match by NestedInstallerType (e.g., zip + wix matches a Wix preference)
return installer.ParseInstallerType() == InstallerType.Zip
&& !string.IsNullOrEmpty(installer.NestedInstallerType)
&& EnumParsers.ParseInstallerType(installer.NestedInstallerType) == requestedType;
}



public static InstallerContext ParseInstallerContext(this Winget.CommunityRepository.Models.WingetInstaller installer)
Expand Down
86 changes: 86 additions & 0 deletions tests/WingetIntune.Tests/PackageInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,91 @@ public void ParsesPowerShell()
var containsSecondLine = info.Description!.Contains("PowerShell commands let you manage computers from the command line.");
Assert.True(containsSecondLine, "Contains second line");
}

[Fact]
public void GetBestInstaller_ZipWithNestedWix_WixPreference_ReturnsZipInstaller()
{
// Arrange: a package with only a zip installer wrapping a wix installer
var packageInfo = new PackageInfo
{
PackageIdentifier = "dotPDN.PaintDotNet",
Version = "5.1.12",
Source = PackageSource.Winget,
Installers = new List<Winget.CommunityRepository.Models.WingetInstaller>
{
new Winget.CommunityRepository.Models.WingetInstaller
{
Architecture = "x64",
Scope = "machine",
InstallerType = "zip",
NestedInstallerType = "wix",
NestedInstallerFiles = new List<Winget.CommunityRepository.Models.WingetNestedInstallerFile>
{
new Winget.CommunityRepository.Models.WingetNestedInstallerFile
{
RelativeFilePath = "PaintDotNet.msi"
}
},
InstallerUrl = "https://github.com/paintdotnet/release/releases/download/v5.1.12/paint.net.5.1.12.zip",
InstallerSha256 = "AABBCCDDEEFF00112233445566778899AABBCCDDEEFF00112233445566778899",
}
}
};

var packageOptions = new PackageOptions
{
Architecture = Architecture.X64,
InstallerContext = InstallerContext.System,
InstallerType = InstallerType.Wix,
};

// Act
var installer = packageInfo.GetBestInstaller(packageOptions);

// Assert: the zip installer (with nested wix) should be selected
Assert.NotNull(installer);
Assert.Equal("zip", installer.InstallerType);
Assert.Equal("wix", installer.NestedInstallerType);
}

[Fact]
public void GetBestInstaller_ZipWithNestedWix_DefaultMsiPreference_ReturnsZipInstaller()
{
// Arrange: a package with only a zip installer wrapping a wix installer
var packageInfo = new PackageInfo
{
PackageIdentifier = "dotPDN.PaintDotNet",
Version = "5.1.12",
Source = PackageSource.Winget,
Installers = new List<Winget.CommunityRepository.Models.WingetInstaller>
{
new Winget.CommunityRepository.Models.WingetInstaller
{
Architecture = "x64",
Scope = "machine",
InstallerType = "zip",
NestedInstallerType = "wix",
NestedInstallerFiles = new List<Winget.CommunityRepository.Models.WingetNestedInstallerFile>
{
new Winget.CommunityRepository.Models.WingetNestedInstallerFile
{
RelativeFilePath = "PaintDotNet.msi"
}
},
InstallerUrl = "https://github.com/paintdotnet/release/releases/download/v5.1.12/paint.net.5.1.12.zip",
InstallerSha256 = "AABBCCDDEEFF00112233445566778899AABBCCDDEEFF00112233445566778899",
}
}
};

// Default options look for Msi; wix-nested-zip should also match since wix IsMsi()
var packageOptions = PackageOptions.Create(); // InstallerType defaults to Msi

// Act
var installer = packageInfo.GetBestInstaller(packageOptions);

// Assert: the zip installer (with nested wix) should be selected via fallback to Unknown
Assert.NotNull(installer);
}
}
}
33 changes: 33 additions & 0 deletions tests/WingetIntune.Tests/WingetManager.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,39 @@ public async Task GetPackageInfoAsync_ZipWithNestedInstaller_ReturnsZipInstaller
Assert.Equal(@"Adobe Acrobat\setup.exe", installer.NestedInstallerFiles!.First().RelativeFilePath);
}

[Fact]
public async Task GetPackageInfoAsync_ZipWithNestedWixInstaller_ReturnsZipInstallerType()
{
var packageId = "dotPDN.PaintDotNet";
var version = "5.1.12";
var source = "winget";
var processManager = Substitute.For<IProcessManager>();
var filemanagerMock = Substitute.For<IFileManager>();
filemanagerMock.DownloadStringAsync(WingetManager.CreateManifestUri(packageId, version, null), true, Arg.Any<CancellationToken>())
.Returns(WingetManagerTestConstants.paintDotNetYaml);
filemanagerMock.DownloadStringAsync(WingetManager.CreateManifestUri(packageId, version, ".installer"), true, Arg.Any<CancellationToken>())
.Returns(WingetManagerTestConstants.paintDotNetInstallYaml);
filemanagerMock.DownloadStringAsync(WingetManager.CreateManifestUri(packageId, version, ".locale.en-US"), true, Arg.Any<CancellationToken>())
.Returns(WingetManagerTestConstants.paintDotNetLocaleYaml);

var wingetManager = new WingetManager(logger, processManager, filemanagerMock);
var info = await wingetManager.GetPackageInfoAsync(packageId, version, source);

Assert.Equal(packageId, info.PackageIdentifier);
Assert.Equal(version, info.Version);
Assert.Equal(InstallerType.Zip, info.InstallerType);
Assert.NotEmpty(info.Installers!);

// Verify NestedInstallerType was propagated to the installer
var installer = info.Installers!.First();
Assert.Equal("zip", installer.InstallerType);
Assert.Equal("wix", installer.NestedInstallerType);
Assert.NotNull(installer.NestedInstallerFiles);
Assert.Single(installer.NestedInstallerFiles!);
Assert.Equal("PaintDotNet.msi", installer.NestedInstallerFiles!.First().RelativeFilePath);
}


[Fact]
public async Task GetPackageInfoAsync_ParsesResponse_StoreResult()
{
Expand Down
35 changes: 35 additions & 0 deletions tests/WingetIntune.Tests/WingetManagerTestConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,4 +242,39 @@ Encontrado Oh My Posh [JanDeDobbeleer.OhMyPosh]
ShortDescription: Adobe Acrobat Pro PDF editor
ManifestType: defaultLocale
ManifestVersion: 1.4.0";

// dotPDN PaintDotNet - a package with InstallerType: zip and NestedInstallerType: wix (for system installs)
internal const string paintDotNetYaml = @"PackageIdentifier: dotPDN.PaintDotNet
PackageVersion: 5.1.12
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.6.0";

internal const string paintDotNetInstallYaml = @"PackageIdentifier: dotPDN.PaintDotNet
PackageVersion: 5.1.12
InstallerType: zip
NestedInstallerType: wix
NestedInstallerFiles:
- RelativeFilePath: PaintDotNet.msi
Scope: machine
InstallModes:
- interactive
- silent
Installers:
- Architecture: x64
InstallerUrl: https://github.com/paintdotnet/release/releases/download/v5.1.12/paint.net.5.1.12.zip
InstallerSha256: AABBCCDDEEFF00112233445566778899AABBCCDDEEFF00112233445566778899 # fake hash for test purposes
ManifestType: installer
ManifestVersion: 1.6.0";

internal const string paintDotNetLocaleYaml = @"PackageIdentifier: dotPDN.PaintDotNet
PackageVersion: 5.1.12
PackageLocale: en-US
Publisher: dotPDN LLC
PublisherUrl: https://www.getpaint.net/
PackageName: Paint.NET
PackageUrl: https://www.getpaint.net/
ShortDescription: Paint.NET is free image and photo editing software
ManifestType: defaultLocale
ManifestVersion: 1.6.0";
}
Loading