up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-28 19:23:54 +02:00
parent d1cbb905f8
commit d040c001ac
36 changed files with 4668 additions and 9 deletions

View File

@@ -0,0 +1,27 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageReference Include="coverlet.collector" Version="6.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS/StellaOps.Scanner.Analyzers.OS.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS.Windows.WinSxS/StellaOps.Scanner.Analyzers.OS.Windows.WinSxS.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,272 @@
using Xunit;
namespace StellaOps.Scanner.Analyzers.OS.Windows.WinSxS.Tests;
public class WinSxSManifestParserTests
{
private readonly WinSxSManifestParser _parser = new();
[Fact]
public void Parse_WithValidManifest_ExtractsMetadata()
{
// Arrange
var manifestPath = CreateTestManifest(@"<?xml version=""1.0"" encoding=""UTF-8""?>
<assembly xmlns=""urn:schemas-microsoft-com:asm.v1"" manifestVersion=""1.0"">
<assemblyIdentity
name=""Microsoft.Windows.Common-Controls""
version=""6.0.0.0""
processorArchitecture=""x86""
publicKeyToken=""6595b64144ccf1df""
language=""*""
type=""win32"" />
<file name=""comctl32.dll"" hash=""abcd1234"" hashalg=""SHA256"" size=""12345"" />
</assembly>");
try
{
// Act
var result = _parser.Parse(manifestPath, CancellationToken.None);
// Assert
Assert.NotNull(result);
Assert.Equal("Microsoft.Windows.Common-Controls", result.Name);
Assert.Equal("6.0.0.0", result.Version);
Assert.Equal("x86", result.ProcessorArchitecture);
Assert.Equal("6595b64144ccf1df", result.PublicKeyToken);
Assert.Equal("*", result.Language);
Assert.Equal("win32", result.Type);
Assert.Single(result.Files);
Assert.Equal("comctl32.dll", result.Files[0].Name);
}
finally
{
CleanupTestManifest(manifestPath);
}
}
[Fact]
public void Parse_WithAmd64Architecture_ExtractsCorrectly()
{
// Arrange
var manifestPath = CreateTestManifest(@"<?xml version=""1.0"" encoding=""UTF-8""?>
<assembly xmlns=""urn:schemas-microsoft-com:asm.v1"" manifestVersion=""1.0"">
<assemblyIdentity
name=""Microsoft.Windows.SystemCompatible""
version=""10.0.19041.1""
processorArchitecture=""amd64""
publicKeyToken=""31bf3856ad364e35"" />
</assembly>");
try
{
// Act
var result = _parser.Parse(manifestPath, CancellationToken.None);
// Assert
Assert.NotNull(result);
Assert.Equal("Microsoft.Windows.SystemCompatible", result.Name);
Assert.Equal("amd64", result.ProcessorArchitecture);
Assert.Equal("31bf3856ad364e35", result.PublicKeyToken);
}
finally
{
CleanupTestManifest(manifestPath);
}
}
[Fact]
public void Parse_WithMultipleFiles_ExtractsAllFiles()
{
// Arrange
var manifestPath = CreateTestManifest(@"<?xml version=""1.0"" encoding=""UTF-8""?>
<assembly xmlns=""urn:schemas-microsoft-com:asm.v1"" manifestVersion=""1.0"">
<assemblyIdentity name=""TestAssembly"" version=""1.0.0.0"" processorArchitecture=""x86"" />
<file name=""file1.dll"" />
<file name=""file2.dll"" />
<file name=""file3.config"" />
</assembly>");
try
{
// Act
var result = _parser.Parse(manifestPath, CancellationToken.None);
// Assert
Assert.NotNull(result);
Assert.Equal(3, result.Files.Count);
Assert.Contains(result.Files, f => f.Name == "file1.dll");
Assert.Contains(result.Files, f => f.Name == "file2.dll");
Assert.Contains(result.Files, f => f.Name == "file3.config");
}
finally
{
CleanupTestManifest(manifestPath);
}
}
[Fact]
public void Parse_WithKbReferenceInFilename_ExtractsKbReference()
{
// Arrange
var manifestPath = CreateTestManifestWithName(
"amd64_microsoft-windows-security_31bf3856ad364e35_10.0.19041.4170_kb5034441.manifest",
@"<?xml version=""1.0"" encoding=""UTF-8""?>
<assembly xmlns=""urn:schemas-microsoft-com:asm.v1"" manifestVersion=""1.0"">
<assemblyIdentity name=""TestSecurity"" version=""10.0.19041.4170"" processorArchitecture=""amd64"" />
</assembly>");
try
{
// Act
var result = _parser.Parse(manifestPath, CancellationToken.None);
// Assert
Assert.NotNull(result);
Assert.Equal("KB5034441", result.KbReference);
}
finally
{
CleanupTestManifest(manifestPath);
}
}
[Fact]
public void Parse_WithNonExistentFile_ReturnsNull()
{
// Act
var result = _parser.Parse("/nonexistent/path/manifest.manifest", CancellationToken.None);
// Assert
Assert.Null(result);
}
[Fact]
public void Parse_WithInvalidXml_ReturnsNull()
{
// Arrange
var manifestPath = CreateTestManifest("not valid xml content");
try
{
// Act
var result = _parser.Parse(manifestPath, CancellationToken.None);
// Assert
Assert.Null(result);
}
finally
{
CleanupTestManifest(manifestPath);
}
}
[Fact]
public void Parse_WithMissingAssemblyIdentity_ReturnsNull()
{
// Arrange
var manifestPath = CreateTestManifest(@"<?xml version=""1.0"" encoding=""UTF-8""?>
<assembly xmlns=""urn:schemas-microsoft-com:asm.v1"" manifestVersion=""1.0"">
<file name=""something.dll"" />
</assembly>");
try
{
// Act
var result = _parser.Parse(manifestPath, CancellationToken.None);
// Assert
Assert.Null(result);
}
finally
{
CleanupTestManifest(manifestPath);
}
}
[Fact]
public void Parse_WithEmptyPath_ReturnsNull()
{
// Act
var result = _parser.Parse(string.Empty, CancellationToken.None);
// Assert
Assert.Null(result);
}
[Fact]
public void BuildAssemblyIdentityString_BuildsCorrectFormat()
{
// Arrange
var metadata = new WinSxSAssemblyMetadata(
Name: "Microsoft.Windows.Common-Controls",
Version: "6.0.0.0",
ProcessorArchitecture: "x86",
PublicKeyToken: "6595b64144ccf1df",
Language: "en-us",
Type: "win32",
VersionScope: null,
ManifestPath: "/test/manifest.manifest",
CatalogPath: null,
CatalogThumbprint: null,
KbReference: null,
Files: []);
// Act
var result = WinSxSManifestParser.BuildAssemblyIdentityString(metadata);
// Assert
Assert.Equal("microsoft.windows.common-controls_6.0.0.0_x86_6595b64144ccf1df_en-us", result);
}
[Fact]
public void BuildAssemblyIdentityString_WithNeutralLanguage_OmitsLanguage()
{
// Arrange
var metadata = new WinSxSAssemblyMetadata(
Name: "TestAssembly",
Version: "1.0.0.0",
ProcessorArchitecture: "amd64",
PublicKeyToken: null,
Language: "*",
Type: null,
VersionScope: null,
ManifestPath: "/test/manifest.manifest",
CatalogPath: null,
CatalogThumbprint: null,
KbReference: null,
Files: []);
// Act
var result = WinSxSManifestParser.BuildAssemblyIdentityString(metadata);
// Assert
Assert.Equal("testassembly_1.0.0.0_amd64", result);
}
private static string CreateTestManifest(string content)
{
var tempDir = Path.Combine(Path.GetTempPath(), "winsxs-test-" + Guid.NewGuid().ToString("N")[..8]);
Directory.CreateDirectory(tempDir);
var manifestPath = Path.Combine(tempDir, "test.manifest");
File.WriteAllText(manifestPath, content);
return manifestPath;
}
private static string CreateTestManifestWithName(string fileName, string content)
{
var tempDir = Path.Combine(Path.GetTempPath(), "winsxs-test-" + Guid.NewGuid().ToString("N")[..8]);
Directory.CreateDirectory(tempDir);
var manifestPath = Path.Combine(tempDir, fileName);
File.WriteAllText(manifestPath, content);
return manifestPath;
}
private static void CleanupTestManifest(string manifestPath)
{
var directory = Path.GetDirectoryName(manifestPath);
if (!string.IsNullOrEmpty(directory) && Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}

View File

@@ -0,0 +1,297 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Scanner.Analyzers.OS.Windows.WinSxS;
using Xunit;
namespace StellaOps.Scanner.Analyzers.OS.Windows.WinSxS.Tests;
public class WinSxSPackageAnalyzerTests
{
private readonly WinSxSPackageAnalyzer _analyzer;
private readonly ILogger _logger;
public WinSxSPackageAnalyzerTests()
{
_logger = NullLoggerFactory.Instance.CreateLogger<WinSxSPackageAnalyzer>();
_analyzer = new WinSxSPackageAnalyzer((ILogger<WinSxSPackageAnalyzer>)_logger);
}
private OSPackageAnalyzerContext CreateContext(string rootPath)
{
return new OSPackageAnalyzerContext(
rootPath,
workspacePath: null,
TimeProvider.System,
_logger);
}
[Fact]
public void AnalyzerId_ReturnsCorrectValue()
{
Assert.Equal("windows-winsxs", _analyzer.AnalyzerId);
}
[Fact]
public async Task AnalyzeAsync_WithNoWinSxSDirectory_ReturnsEmptyList()
{
// Arrange
var tempDir = Path.Combine(Path.GetTempPath(), "winsxs-analyzer-test-" + Guid.NewGuid().ToString("N")[..8]);
Directory.CreateDirectory(tempDir);
try
{
var context = CreateContext(tempDir);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert
Assert.NotNull(result);
Assert.Empty(result.Packages);
}
finally
{
Directory.Delete(tempDir, recursive: true);
}
}
[Fact]
public async Task AnalyzeAsync_WithManifestFiles_ReturnsPackageRecords()
{
// Arrange
var tempDir = Path.Combine(Path.GetTempPath(), "winsxs-analyzer-test-" + Guid.NewGuid().ToString("N")[..8]);
var manifestsDir = Path.Combine(tempDir, "Windows", "WinSxS", "Manifests");
Directory.CreateDirectory(manifestsDir);
// Create test manifest files
CreateTestManifest(manifestsDir, "amd64_test-assembly1_6.0.0.0.manifest",
"Test.Assembly1", "6.0.0.0", "amd64");
CreateTestManifest(manifestsDir, "x86_test-assembly2_1.2.3.4.manifest",
"Test.Assembly2", "1.2.3.4", "x86");
try
{
var context = CreateContext(tempDir);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert
Assert.Equal(2, result.Packages.Count);
var assembly1 = result.Packages.FirstOrDefault(r => r.Name == "Test.Assembly1");
Assert.NotNull(assembly1);
Assert.Equal("6.0.0.0", assembly1.Version);
Assert.Equal("amd64", assembly1.Architecture);
Assert.Equal(PackageEvidenceSource.WindowsWinSxS, assembly1.EvidenceSource);
Assert.StartsWith("pkg:generic/windows-winsxs/test.assembly1@6.0.0.0", assembly1.PackageUrl);
var assembly2 = result.Packages.FirstOrDefault(r => r.Name == "Test.Assembly2");
Assert.NotNull(assembly2);
Assert.Equal("1.2.3.4", assembly2.Version);
Assert.Equal("x86", assembly2.Architecture);
}
finally
{
Directory.Delete(tempDir, recursive: true);
}
}
[Fact]
public async Task AnalyzeAsync_ExtractsVendorMetadata()
{
// Arrange
var tempDir = Path.Combine(Path.GetTempPath(), "winsxs-analyzer-test-" + Guid.NewGuid().ToString("N")[..8]);
var manifestsDir = Path.Combine(tempDir, "Windows", "WinSxS", "Manifests");
Directory.CreateDirectory(manifestsDir);
CreateTestManifest(manifestsDir, "amd64_microsoft-test_6595b64144ccf1df_10.0.0.0_en-us.manifest",
"Microsoft.Test", "10.0.0.0", "amd64", "6595b64144ccf1df", "en-us", "win32");
try
{
var context = CreateContext(tempDir);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert
Assert.Single(result.Packages);
var record = result.Packages[0];
Assert.Equal("Microsoft.Test", record.VendorMetadata["winsxs:name"]);
Assert.Equal("10.0.0.0", record.VendorMetadata["winsxs:version"]);
Assert.Equal("amd64", record.VendorMetadata["winsxs:arch"]);
Assert.Equal("6595b64144ccf1df", record.VendorMetadata["winsxs:public_key_token"]);
Assert.Equal("en-us", record.VendorMetadata["winsxs:language"]);
Assert.Equal("win32", record.VendorMetadata["winsxs:type"]);
}
finally
{
Directory.Delete(tempDir, recursive: true);
}
}
[Fact]
public async Task AnalyzeAsync_ExtractsPublisherFromAssemblyName()
{
// Arrange
var tempDir = Path.Combine(Path.GetTempPath(), "winsxs-analyzer-test-" + Guid.NewGuid().ToString("N")[..8]);
var manifestsDir = Path.Combine(tempDir, "Windows", "WinSxS", "Manifests");
Directory.CreateDirectory(manifestsDir);
CreateTestManifest(manifestsDir, "amd64_microsoft-windows-component_1.0.0.0.manifest",
"Microsoft.Windows.Component", "1.0.0.0", "amd64");
try
{
var context = CreateContext(tempDir);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert
Assert.Single(result.Packages);
Assert.Equal("Microsoft", result.Packages[0].SourcePackage);
}
finally
{
Directory.Delete(tempDir, recursive: true);
}
}
[Fact]
public async Task AnalyzeAsync_IncludesFileEvidence()
{
// Arrange
var tempDir = Path.Combine(Path.GetTempPath(), "winsxs-analyzer-test-" + Guid.NewGuid().ToString("N")[..8]);
var manifestsDir = Path.Combine(tempDir, "Windows", "WinSxS", "Manifests");
Directory.CreateDirectory(manifestsDir);
var manifestPath = Path.Combine(manifestsDir, "amd64_test-assembly_1.0.0.0.manifest");
File.WriteAllText(manifestPath, @"<?xml version=""1.0"" encoding=""UTF-8""?>
<assembly xmlns=""urn:schemas-microsoft-com:asm.v1"" manifestVersion=""1.0"">
<assemblyIdentity name=""Test.Assembly"" version=""1.0.0.0"" processorArchitecture=""amd64"" />
<file name=""test.dll"" hash=""abcdef123456"" hashalg=""SHA256"" size=""54321"" />
</assembly>");
try
{
var context = CreateContext(tempDir);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert
Assert.Single(result.Packages);
var record = result.Packages[0];
// Should have manifest file + declared file
Assert.Equal(2, record.Files.Count);
Assert.Contains(record.Files, f => f.Path.Contains(".manifest"));
Assert.Contains(record.Files, f => f.Path.Contains("test.dll"));
var dllFile = record.Files.First(f => f.Path.Contains("test.dll"));
Assert.Equal("sha256:abcdef123456", dllFile.Sha256);
Assert.Equal(54321L, dllFile.SizeBytes);
}
finally
{
Directory.Delete(tempDir, recursive: true);
}
}
[Fact]
public async Task AnalyzeAsync_WithInvalidManifest_SkipsAndContinues()
{
// Arrange
var tempDir = Path.Combine(Path.GetTempPath(), "winsxs-analyzer-test-" + Guid.NewGuid().ToString("N")[..8]);
var manifestsDir = Path.Combine(tempDir, "Windows", "WinSxS", "Manifests");
Directory.CreateDirectory(manifestsDir);
// Create invalid manifest
File.WriteAllText(Path.Combine(manifestsDir, "invalid.manifest"), "not valid xml");
// Create valid manifest
CreateTestManifest(manifestsDir, "amd64_valid-assembly_1.0.0.0.manifest",
"Valid.Assembly", "1.0.0.0", "amd64");
try
{
var context = CreateContext(tempDir);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert - Only valid manifest should be returned
Assert.Single(result.Packages);
Assert.Equal("Valid.Assembly", result.Packages[0].Name);
}
finally
{
Directory.Delete(tempDir, recursive: true);
}
}
[Fact]
public async Task AnalyzeAsync_ResultsAreSortedDeterministically()
{
// Arrange
var tempDir = Path.Combine(Path.GetTempPath(), "winsxs-analyzer-test-" + Guid.NewGuid().ToString("N")[..8]);
var manifestsDir = Path.Combine(tempDir, "Windows", "WinSxS", "Manifests");
Directory.CreateDirectory(manifestsDir);
// Create manifests in random order
CreateTestManifest(manifestsDir, "amd64_zeta-assembly_1.0.0.0.manifest",
"Zeta.Assembly", "1.0.0.0", "amd64");
CreateTestManifest(manifestsDir, "amd64_alpha-assembly_1.0.0.0.manifest",
"Alpha.Assembly", "1.0.0.0", "amd64");
CreateTestManifest(manifestsDir, "amd64_mid-assembly_1.0.0.0.manifest",
"Mid.Assembly", "1.0.0.0", "amd64");
try
{
var context = CreateContext(tempDir);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert - Results should be sorted
Assert.Equal(3, result.Packages.Count);
Assert.Equal("Alpha.Assembly", result.Packages[0].Name);
Assert.Equal("Mid.Assembly", result.Packages[1].Name);
Assert.Equal("Zeta.Assembly", result.Packages[2].Name);
}
finally
{
Directory.Delete(tempDir, recursive: true);
}
}
private static void CreateTestManifest(
string directory,
string fileName,
string assemblyName,
string version,
string architecture,
string? publicKeyToken = null,
string? language = null,
string? type = null)
{
var manifestPath = Path.Combine(directory, fileName);
var publicKeyAttr = publicKeyToken is not null ? $@" publicKeyToken=""{publicKeyToken}""" : "";
var languageAttr = language is not null ? $@" language=""{language}""" : "";
var typeAttr = type is not null ? $@" type=""{type}""" : "";
var content = $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<assembly xmlns=""urn:schemas-microsoft-com:asm.v1"" manifestVersion=""1.0"">
<assemblyIdentity
name=""{assemblyName}""
version=""{version}""
processorArchitecture=""{architecture}""{publicKeyAttr}{languageAttr}{typeAttr} />
</assembly>";
File.WriteAllText(manifestPath, content);
}
}