236 lines
7.4 KiB
C#
236 lines
7.4 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using StellaOps.Scanner.Analyzers.OS.Homebrew;
|
|
using Xunit;
|
|
|
|
using StellaOps.TestKit;
|
|
namespace StellaOps.Scanner.Analyzers.OS.Homebrew.Tests;
|
|
|
|
public sealed class HomebrewPackageAnalyzerTests
|
|
{
|
|
private static readonly string FixturesRoot = Path.Combine(
|
|
AppContext.BaseDirectory,
|
|
"Fixtures");
|
|
|
|
private readonly HomebrewPackageAnalyzer _analyzer;
|
|
private readonly ILogger _logger;
|
|
|
|
public HomebrewPackageAnalyzerTests()
|
|
{
|
|
_logger = NullLoggerFactory.Instance.CreateLogger<HomebrewPackageAnalyzer>();
|
|
_analyzer = new HomebrewPackageAnalyzer((ILogger<HomebrewPackageAnalyzer>)_logger);
|
|
}
|
|
|
|
private OSPackageAnalyzerContext CreateContext(string rootPath)
|
|
{
|
|
return new OSPackageAnalyzerContext(
|
|
rootPath,
|
|
workspacePath: null,
|
|
TimeProvider.System,
|
|
_logger);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void AnalyzerId_ReturnsHomebrew()
|
|
{
|
|
Assert.Equal("homebrew", _analyzer.AnalyzerId);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task AnalyzeAsync_WithValidCellar_ReturnsPackages()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(FixturesRoot);
|
|
|
|
// Act
|
|
var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal("homebrew", result.AnalyzerId);
|
|
Assert.True(result.Packages.Count > 0, "Expected at least one package");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task AnalyzeAsync_FindsIntelCellarPackages()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(FixturesRoot);
|
|
|
|
// Act
|
|
var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
var openssl = result.Packages.FirstOrDefault(p => p.Name == "openssl@3");
|
|
Assert.NotNull(openssl);
|
|
Assert.Equal("3.1.0", openssl.Version);
|
|
Assert.Equal("x86_64", openssl.Architecture);
|
|
Assert.Contains("pkg:brew/homebrew%2Fcore/openssl%403@3.1.0", openssl.PackageUrl);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task AnalyzeAsync_FindsAppleSiliconCellarPackages()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(FixturesRoot);
|
|
|
|
// Act
|
|
var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
var jq = result.Packages.FirstOrDefault(p => p.Name == "jq");
|
|
Assert.NotNull(jq);
|
|
Assert.Equal("1.7", jq.Version);
|
|
Assert.Equal("arm64", jq.Architecture);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task AnalyzeAsync_PackageWithRevision_IncludesRevisionInPurl()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(FixturesRoot);
|
|
|
|
// Act
|
|
var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
var wget = result.Packages.FirstOrDefault(p => p.Name == "wget");
|
|
Assert.NotNull(wget);
|
|
Assert.Contains("?revision=1", wget.PackageUrl);
|
|
Assert.Equal("1", wget.Release);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task AnalyzeAsync_ExtractsDependencies()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(FixturesRoot);
|
|
|
|
// Act
|
|
var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
var wget = result.Packages.FirstOrDefault(p => p.Name == "wget");
|
|
Assert.NotNull(wget);
|
|
Assert.Contains("openssl@3", wget.Depends);
|
|
Assert.Contains("gettext", wget.Depends);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task AnalyzeAsync_ExtractsVendorMetadata()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(FixturesRoot);
|
|
|
|
// Act
|
|
var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
var openssl = result.Packages.FirstOrDefault(p => p.Name == "openssl@3");
|
|
Assert.NotNull(openssl);
|
|
Assert.Equal("homebrew/core", openssl.VendorMetadata["brew:tap"]);
|
|
Assert.Equal("true", openssl.VendorMetadata["brew:poured_from_bottle"]);
|
|
Assert.Equal("Cryptography and SSL/TLS Toolkit", openssl.VendorMetadata["description"]);
|
|
Assert.Equal("https://openssl.org/", openssl.VendorMetadata["homepage"]);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task AnalyzeAsync_SetsEvidenceSourceToHomebrewCellar()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(FixturesRoot);
|
|
|
|
// Act
|
|
var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
foreach (var package in result.Packages)
|
|
{
|
|
Assert.Equal(PackageEvidenceSource.HomebrewCellar, package.EvidenceSource);
|
|
}
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task AnalyzeAsync_DiscoversBinFiles()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(FixturesRoot);
|
|
|
|
// Act
|
|
var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
var wget = result.Packages.FirstOrDefault(p => p.Name == "wget");
|
|
Assert.NotNull(wget);
|
|
Assert.Contains(wget.Files, f => f.Path.Contains("wget"));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task AnalyzeAsync_ResultsAreDeterministicallySorted()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(FixturesRoot);
|
|
|
|
// Act
|
|
var result1 = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
|
var result2 = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
Assert.Equal(result1.Packages.Count, result2.Packages.Count);
|
|
for (int i = 0; i < result1.Packages.Count; i++)
|
|
{
|
|
Assert.Equal(result1.Packages[i].PackageUrl, result2.Packages[i].PackageUrl);
|
|
}
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task AnalyzeAsync_NoCellar_ReturnsEmptyPackages()
|
|
{
|
|
// Arrange - use temp directory without Cellar structure
|
|
var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
|
Directory.CreateDirectory(tempPath);
|
|
|
|
try
|
|
{
|
|
var context = CreateContext(tempPath);
|
|
|
|
// Act
|
|
var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
Assert.Empty(result.Packages);
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(tempPath, recursive: true);
|
|
}
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task AnalyzeAsync_PopulatesTelemetry()
|
|
{
|
|
// Arrange
|
|
var context = CreateContext(FixturesRoot);
|
|
|
|
// Act
|
|
var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
Assert.NotNull(result.Telemetry);
|
|
Assert.True(result.Telemetry.PackageCount > 0);
|
|
Assert.True(result.Telemetry.Duration > TimeSpan.Zero);
|
|
}
|
|
}
|