Files
git.stella-ops.org/src/__Tests/Tools/FixtureHarvester/SbomGoldenCommandTests.cs

357 lines
10 KiB
C#

// <copyright file="SbomGoldenCommandTests.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
// </copyright>
using System.Text.Json;
using Xunit;
namespace StellaOps.Testing.FixtureHarvester.Tests;
/// <summary>
/// Unit tests for SbomGoldenCommand.
/// @sprint SPRINT_20251229_004_LIB_fixture_harvester (FH-007)
/// </summary>
public sealed class SbomGoldenCommandTests : IDisposable
{
private readonly string _testOutputDir;
public SbomGoldenCommandTests()
{
_testOutputDir = Path.Combine(Path.GetTempPath(), $"fixture-harvester-test-{Guid.NewGuid():N}");
Directory.CreateDirectory(_testOutputDir);
}
public void Dispose()
{
if (Directory.Exists(_testOutputDir))
{
Directory.Delete(_testOutputDir, recursive: true);
}
}
[Fact]
public void KnownImages_ContainsExpectedEntries()
{
// Arrange
var expectedImages = new[] { "alpine-minimal", "debian-slim", "distroless-static", "scratch-go" };
// Act & Assert
foreach (var image in expectedImages)
{
Assert.True(KnownImagesContainsTestHelper(image), $"Should contain {image}");
}
}
[Theory]
[InlineData("cyclonedx", "cdx.json")]
[InlineData("cyclonedx-json", "cdx.json")]
[InlineData("spdx", "spdx.json")]
[InlineData("spdx-json", "spdx.json")]
[InlineData("unknown", "json")]
public void GetFormatExtension_ReturnsCorrectExtension(string format, string expectedExt)
{
// Arrange & Act
var result = GetFormatExtensionTestHelper(format);
// Assert
Assert.Equal(expectedExt, result);
}
[Fact]
public void GenerateCycloneDxSample_HasRequiredFields()
{
// Arrange
var imageDef = new TestGoldenImageDefinition
{
Id = "test-image",
ImageRef = "alpine:3.19",
Description = "Test image",
ExpectedPackages = 5,
};
// Act
var sbom = GenerateCycloneDxSampleTestHelper(imageDef);
var json = JsonSerializer.Serialize(sbom);
// Assert
Assert.Contains("CycloneDX", json);
Assert.Contains("specVersion", json);
Assert.Contains("1.6", json);
Assert.Contains("metadata", json);
Assert.Contains("components", json);
Assert.Contains("serialNumber", json);
}
[Fact]
public void GenerateCycloneDxSample_HasCorrectComponentCount()
{
// Arrange
var imageDef = new TestGoldenImageDefinition
{
Id = "test-image",
ImageRef = "alpine:3.19",
Description = "Test image",
ExpectedPackages = 10,
};
// Act
var sbom = GenerateCycloneDxSampleTestHelper(imageDef);
// Assert
Assert.NotNull(sbom.components);
Assert.Equal(10, sbom.components.Count);
}
[Fact]
public void GenerateCycloneDxSample_ComponentsHavePurl()
{
// Arrange
var imageDef = new TestGoldenImageDefinition
{
Id = "test-image",
ImageRef = "alpine:3.19",
Description = "Test image",
ExpectedPackages = 3,
};
// Act
var sbom = GenerateCycloneDxSampleTestHelper(imageDef);
var json = JsonSerializer.Serialize(sbom);
// Assert
Assert.Contains("pkg:apk", json);
}
[Fact]
public void GenerateSpdxSample_HasRequiredFields()
{
// Arrange
var imageDef = new TestGoldenImageDefinition
{
Id = "test-image",
ImageRef = "alpine:3.19",
Description = "Test image",
ExpectedPackages = 5,
};
// Act
var sbom = GenerateSpdxSampleTestHelper(imageDef);
var json = JsonSerializer.Serialize(sbom);
// Assert
Assert.Contains("SPDX-2.3", json);
Assert.Contains("CC0-1.0", json);
Assert.Contains("SPDXRef-DOCUMENT", json);
Assert.Contains("creationInfo", json);
Assert.Contains("packages", json);
}
[Fact]
public void GenerateSpdxSample_PackagesHaveSpdxId()
{
// Arrange
var imageDef = new TestGoldenImageDefinition
{
Id = "test-image",
ImageRef = "alpine:3.19",
Description = "Test image",
ExpectedPackages = 3,
};
// Act
var sbom = GenerateSpdxSampleTestHelper(imageDef);
var json = JsonSerializer.Serialize(sbom);
// Assert
Assert.Contains("SPDXRef-Package", json);
}
[Fact]
public void CountPackages_CycloneDx_ReturnsCorrectCount()
{
// Arrange
var sbom = new
{
bomFormat = "CycloneDX",
components = new[]
{
new { name = "pkg1" },
new { name = "pkg2" },
new { name = "pkg3" },
}
};
var json = JsonSerializer.Serialize(sbom);
// Act
var count = CountPackagesTestHelper(json, "cyclonedx");
// Assert
Assert.Equal(3, count);
}
[Fact]
public void CountPackages_Spdx_ReturnsCorrectCount()
{
// Arrange
var sbom = new
{
spdxVersion = "SPDX-2.3",
packages = new[]
{
new { SPDXID = "SPDXRef-Package-1" },
new { SPDXID = "SPDXRef-Package-2" },
}
};
var json = JsonSerializer.Serialize(sbom);
// Act
var count = CountPackagesTestHelper(json, "spdx");
// Assert
Assert.Equal(2, count);
}
[Fact]
public void CountPackages_InvalidJson_ReturnsZero()
{
// Arrange
var invalidJson = "not valid json";
// Act
var count = CountPackagesTestHelper(invalidJson, "cyclonedx");
// Assert
Assert.Equal(0, count);
}
// Helper types and methods
private class TestGoldenImageDefinition
{
public string Id { get; set; } = string.Empty;
public string ImageRef { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public int ExpectedPackages { get; set; }
}
private static bool KnownImagesContainsTestHelper(string image)
{
var knownImages = new Dictionary<string, bool>
{
["alpine-minimal"] = true,
["debian-slim"] = true,
["distroless-static"] = true,
["scratch-go"] = true,
};
return knownImages.ContainsKey(image.ToLowerInvariant());
}
private static string GetFormatExtensionTestHelper(string format)
{
return format.ToLowerInvariant() switch
{
"cyclonedx" or "cyclonedx-json" => "cdx.json",
"spdx" or "spdx-json" => "spdx.json",
_ => "json",
};
}
private static dynamic GenerateCycloneDxSampleTestHelper(TestGoldenImageDefinition imageDef)
{
var components = new List<object>();
for (int i = 0; i < Math.Max(1, imageDef.ExpectedPackages); i++)
{
components.Add(new
{
type = "library",
name = $"sample-package-{i + 1}",
version = $"1.{i}.0",
purl = $"pkg:apk/alpine/sample-package-{i + 1}@1.{i}.0",
});
}
return new
{
bomFormat = "CycloneDX",
specVersion = "1.6",
serialNumber = $"urn:uuid:{Guid.NewGuid()}",
version = 1,
metadata = new
{
timestamp = DateTime.UtcNow.ToString("O"),
tools = new[]
{
new { vendor = "StellaOps", name = "FixtureHarvester", version = "1.0.0" }
},
component = new
{
type = "container",
name = imageDef.ImageRef.Split(':')[0].Split('/').Last(),
version = imageDef.ImageRef.Contains(':') ? imageDef.ImageRef.Split(':').Last() : "latest",
purl = $"pkg:oci/{imageDef.ImageRef.Replace(':', '@')}",
},
},
components = components,
};
}
private static dynamic GenerateSpdxSampleTestHelper(TestGoldenImageDefinition imageDef)
{
var packages = new List<object>();
for (int i = 0; i < Math.Max(1, imageDef.ExpectedPackages); i++)
{
packages.Add(new
{
SPDXID = $"SPDXRef-Package-{i + 1}",
name = $"sample-package-{i + 1}",
versionInfo = $"1.{i}.0",
downloadLocation = "NOASSERTION",
filesAnalyzed = false,
});
}
return new
{
spdxVersion = "SPDX-2.3",
dataLicense = "CC0-1.0",
SPDXID = "SPDXRef-DOCUMENT",
name = imageDef.Id,
documentNamespace = $"https://stellaops.dev/spdx/{imageDef.Id}",
creationInfo = new
{
created = DateTime.UtcNow.ToString("O"),
creators = new[] { "Tool: StellaOps-FixtureHarvester-1.0.0" },
},
packages = packages,
};
}
private static int CountPackagesTestHelper(string sbomContent, string format)
{
try
{
var doc = JsonDocument.Parse(sbomContent);
if (format.Contains("cyclonedx", StringComparison.OrdinalIgnoreCase))
{
if (doc.RootElement.TryGetProperty("components", out var components))
{
return components.GetArrayLength();
}
}
else if (format.Contains("spdx", StringComparison.OrdinalIgnoreCase))
{
if (doc.RootElement.TryGetProperty("packages", out var packages))
{
return packages.GetArrayLength();
}
}
}
catch
{
// Parse error
}
return 0;
}
}