Frontend gaps fill work. Testing fixes work. Auditing in progress.
This commit is contained in:
356
src/__Tests/Tools/FixtureHarvester/SbomGoldenCommandTests.cs
Normal file
356
src/__Tests/Tools/FixtureHarvester/SbomGoldenCommandTests.cs
Normal file
@@ -0,0 +1,356 @@
|
||||
// <copyright file="SbomGoldenCommandTests.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// </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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user