up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-28 09:40:40 +02:00
parent 1c6730a1d2
commit 05da719048
206 changed files with 34741 additions and 1751 deletions

View File

@@ -0,0 +1,22 @@
{
"name": "jq",
"versions": {
"stable": "1.7"
},
"revision": 0,
"tap": "homebrew/core",
"poured_from_bottle": true,
"time": 1700000000,
"installed_as_dependency": false,
"installed_on_request": true,
"runtime_dependencies": [],
"build_dependencies": [],
"source": {
"url": "https://github.com/jqlang/jq/releases/download/jq-1.7/jq-1.7.tar.gz",
"checksum": "sha256:jq17hash"
},
"desc": "Lightweight and flexible command-line JSON processor",
"homepage": "https://jqlang.github.io/jq/",
"license": "MIT",
"arch": "arm64"
}

View File

@@ -0,0 +1,27 @@
{
"name": "openssl@3",
"versions": {
"stable": "3.1.0"
},
"revision": 0,
"tap": "homebrew/core",
"poured_from_bottle": true,
"time": 1699000000,
"installed_as_dependency": false,
"installed_on_request": true,
"runtime_dependencies": [
{
"full_name": "ca-certificates",
"version": "2023-01-10"
}
],
"build_dependencies": [],
"source": {
"url": "https://www.openssl.org/source/openssl-3.1.0.tar.gz",
"checksum": "sha256:aafde89dd0e91c3d0e87c4b4e3f4d4c9f8f5a6e2b3d4c5a6f7e8d9c0a1b2c3d4e5"
},
"desc": "Cryptography and SSL/TLS Toolkit",
"homepage": "https://openssl.org/",
"license": "Apache-2.0",
"arch": "x86_64"
}

View File

@@ -0,0 +1,31 @@
{
"name": "wget",
"versions": {
"stable": "1.21.4"
},
"revision": 1,
"tap": "homebrew/core",
"poured_from_bottle": true,
"time": 1698500000,
"installed_as_dependency": true,
"installed_on_request": false,
"runtime_dependencies": [
{
"full_name": "openssl@3",
"version": "3.1.0"
},
{
"full_name": "gettext",
"version": "0.21.1"
}
],
"build_dependencies": [],
"source": {
"url": "https://ftp.gnu.org/gnu/wget/wget-1.21.4.tar.gz",
"checksum": "sha256:abc123def456"
},
"desc": "Internet file retriever",
"homepage": "https://www.gnu.org/software/wget/",
"license": "GPL-3.0-or-later",
"arch": "x86_64"
}

View File

@@ -0,0 +1,222 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Scanner.Analyzers.OS.Homebrew;
using Xunit;
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);
}
[Fact]
public void AnalyzerId_ReturnsHomebrew()
{
Assert.Equal("homebrew", _analyzer.AnalyzerId);
}
[Fact]
public async Task AnalyzeAsync_WithValidCellar_ReturnsPackages()
{
// Arrange
var context = CreateContext(FixturesRoot);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert
Assert.NotNull(result);
Assert.Equal("homebrew", result.AnalyzerId);
Assert.True(result.Packages.Count > 0, "Expected at least one package");
}
[Fact]
public async Task AnalyzeAsync_FindsIntelCellarPackages()
{
// Arrange
var context = CreateContext(FixturesRoot);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// 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);
}
[Fact]
public async Task AnalyzeAsync_FindsAppleSiliconCellarPackages()
{
// Arrange
var context = CreateContext(FixturesRoot);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert
var jq = result.Packages.FirstOrDefault(p => p.Name == "jq");
Assert.NotNull(jq);
Assert.Equal("1.7", jq.Version);
Assert.Equal("arm64", jq.Architecture);
}
[Fact]
public async Task AnalyzeAsync_PackageWithRevision_IncludesRevisionInPurl()
{
// Arrange
var context = CreateContext(FixturesRoot);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert
var wget = result.Packages.FirstOrDefault(p => p.Name == "wget");
Assert.NotNull(wget);
Assert.Contains("?revision=1", wget.PackageUrl);
Assert.Equal("1", wget.Release);
}
[Fact]
public async Task AnalyzeAsync_ExtractsDependencies()
{
// Arrange
var context = CreateContext(FixturesRoot);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert
var wget = result.Packages.FirstOrDefault(p => p.Name == "wget");
Assert.NotNull(wget);
Assert.Contains("openssl@3", wget.Depends);
Assert.Contains("gettext", wget.Depends);
}
[Fact]
public async Task AnalyzeAsync_ExtractsVendorMetadata()
{
// Arrange
var context = CreateContext(FixturesRoot);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// 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"]);
}
[Fact]
public async Task AnalyzeAsync_SetsEvidenceSourceToHomebrewCellar()
{
// Arrange
var context = CreateContext(FixturesRoot);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert
foreach (var package in result.Packages)
{
Assert.Equal(PackageEvidenceSource.HomebrewCellar, package.EvidenceSource);
}
}
[Fact]
public async Task AnalyzeAsync_DiscoversBinFiles()
{
// Arrange
var context = CreateContext(FixturesRoot);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert
var wget = result.Packages.FirstOrDefault(p => p.Name == "wget");
Assert.NotNull(wget);
Assert.Contains(wget.Files, f => f.Path.Contains("wget"));
}
[Fact]
public async Task AnalyzeAsync_ResultsAreDeterministicallySorted()
{
// Arrange
var context = CreateContext(FixturesRoot);
// Act
var result1 = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
var result2 = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// 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);
}
}
[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, CancellationToken.None);
// Assert
Assert.Empty(result.Packages);
}
finally
{
Directory.Delete(tempPath, recursive: true);
}
}
[Fact]
public async Task AnalyzeAsync_PopulatesTelemetry()
{
// Arrange
var context = CreateContext(FixturesRoot);
// Act
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
// Assert
Assert.NotNull(result.Telemetry);
Assert.True(result.Telemetry.PackageCount > 0);
Assert.True(result.Telemetry.Duration > TimeSpan.Zero);
}
}

View File

@@ -0,0 +1,269 @@
using System.Text;
using StellaOps.Scanner.Analyzers.OS.Homebrew;
using Xunit;
namespace StellaOps.Scanner.Analyzers.OS.Homebrew.Tests;
public sealed class HomebrewReceiptParserTests
{
private readonly HomebrewReceiptParser _parser = new();
[Fact]
public void Parse_ValidReceipt_ReturnsExpectedValues()
{
// Arrange
var json = """
{
"name": "openssl@3",
"versions": { "stable": "3.1.0" },
"revision": 0,
"tap": "homebrew/core",
"poured_from_bottle": true,
"time": 1699000000,
"installed_as_dependency": false,
"installed_on_request": true,
"runtime_dependencies": [{ "full_name": "ca-certificates", "version": "2023-01-10" }],
"desc": "Cryptography and SSL/TLS Toolkit",
"homepage": "https://openssl.org/",
"license": "Apache-2.0",
"arch": "x86_64"
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
// Act
var receipt = _parser.Parse(stream);
// Assert
Assert.NotNull(receipt);
Assert.Equal("openssl@3", receipt.Name);
Assert.Equal("3.1.0", receipt.Version);
Assert.Equal(0, receipt.Revision);
Assert.Equal("homebrew/core", receipt.Tap);
Assert.True(receipt.PouredFromBottle);
Assert.False(receipt.InstalledAsDependency);
Assert.True(receipt.InstalledOnRequest);
Assert.Single(receipt.RuntimeDependencies);
Assert.Equal("ca-certificates", receipt.RuntimeDependencies[0]);
Assert.Equal("Cryptography and SSL/TLS Toolkit", receipt.Description);
Assert.Equal("https://openssl.org/", receipt.Homepage);
Assert.Equal("Apache-2.0", receipt.License);
Assert.Equal("x86_64", receipt.Architecture);
}
[Fact]
public void Parse_WithRevision_ReturnsCorrectRevision()
{
// Arrange
var json = """
{
"name": "wget",
"versions": { "stable": "1.21.4" },
"revision": 1,
"tap": "homebrew/core",
"poured_from_bottle": true,
"arch": "x86_64"
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
// Act
var receipt = _parser.Parse(stream);
// Assert
Assert.NotNull(receipt);
Assert.Equal("wget", receipt.Name);
Assert.Equal("1.21.4", receipt.Version);
Assert.Equal(1, receipt.Revision);
}
[Fact]
public void Parse_AppleSilicon_ReturnsArm64Architecture()
{
// Arrange
var json = """
{
"name": "jq",
"versions": { "stable": "1.7" },
"revision": 0,
"tap": "homebrew/core",
"arch": "arm64"
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
// Act
var receipt = _parser.Parse(stream);
// Assert
Assert.NotNull(receipt);
Assert.Equal("arm64", receipt.Architecture);
}
[Fact]
public void Parse_WithSourceInfo_ExtractsSourceUrlAndChecksum()
{
// Arrange
var json = """
{
"name": "test",
"versions": { "stable": "1.0.0" },
"tap": "homebrew/core",
"source": {
"url": "https://example.com/test-1.0.0.tar.gz",
"checksum": "sha256:abcdef123456"
}
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
// Act
var receipt = _parser.Parse(stream);
// Assert
Assert.NotNull(receipt);
Assert.Equal("https://example.com/test-1.0.0.tar.gz", receipt.SourceUrl);
Assert.Equal("sha256:abcdef123456", receipt.SourceChecksum);
}
[Fact]
public void Parse_MultipleDependencies_SortsAlphabetically()
{
// Arrange
var json = """
{
"name": "test",
"versions": { "stable": "1.0.0" },
"tap": "homebrew/core",
"runtime_dependencies": [
{ "full_name": "zlib" },
{ "full_name": "openssl" },
{ "full_name": "libpng" }
]
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
// Act
var receipt = _parser.Parse(stream);
// Assert
Assert.NotNull(receipt);
Assert.Equal(3, receipt.RuntimeDependencies.Count);
Assert.Equal("libpng", receipt.RuntimeDependencies[0]);
Assert.Equal("openssl", receipt.RuntimeDependencies[1]);
Assert.Equal("zlib", receipt.RuntimeDependencies[2]);
}
[Fact]
public void Parse_InvalidJson_ReturnsNull()
{
// Arrange
var invalidJson = "{ invalid json }";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJson));
// Act
var receipt = _parser.Parse(stream);
// Assert
Assert.Null(receipt);
}
[Fact]
public void Parse_EmptyJson_ReturnsNull()
{
// Arrange
var emptyJson = "{}";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(emptyJson));
// Act
var receipt = _parser.Parse(stream);
// Assert
Assert.Null(receipt);
}
[Fact]
public void Parse_MissingName_ReturnsNull()
{
// Arrange
var json = """
{
"versions": { "stable": "1.0.0" },
"tap": "homebrew/core"
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
// Act
var receipt = _parser.Parse(stream);
// Assert
Assert.Null(receipt);
}
[Fact]
public void Parse_TappedFrom_UsesTappedFromOverTap()
{
// Arrange
var json = """
{
"name": "test",
"versions": { "stable": "1.0.0" },
"tap": "homebrew/core",
"tapped_from": "custom/tap"
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
// Act
var receipt = _parser.Parse(stream);
// Assert
Assert.NotNull(receipt);
Assert.Equal("custom/tap", receipt.Tap);
}
[Fact]
public void Parse_FallbackVersion_UsesVersionFieldWhenVersionsStableMissing()
{
// Arrange - older receipt format uses version field directly
var json = """
{
"name": "test",
"version": "2.0.0",
"tap": "homebrew/core"
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
// Act
var receipt = _parser.Parse(stream);
// Assert
Assert.NotNull(receipt);
Assert.Equal("2.0.0", receipt.Version);
}
[Fact]
public void Parse_NormalizesArchitecture_AArch64ToArm64()
{
// Arrange
var json = """
{
"name": "test",
"versions": { "stable": "1.0.0" },
"tap": "homebrew/core",
"arch": "aarch64"
}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
// Act
var receipt = _parser.Parse(stream);
// Assert
Assert.NotNull(receipt);
Assert.Equal("arm64", receipt.Architecture);
}
}

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.Homebrew/StellaOps.Scanner.Analyzers.OS.Homebrew.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>