using System.Linq; using StellaOps.Concelier.Merge.Services; using StellaOps.Concelier.Models; using StellaOps.TestKit; namespace StellaOps.Concelier.Merge.Tests; public sealed class CanonicalHashCalculatorTests { private static readonly Advisory SampleAdvisory = new( advisoryKey: "CVE-2024-0001", title: "Sample advisory", summary: "A sample summary", language: "EN", published: DateTimeOffset.Parse("2024-01-01T00:00:00Z"), modified: DateTimeOffset.Parse("2024-01-02T00:00:00Z"), severity: "high", exploitKnown: true, aliases: new[] { "GHSA-xyz", "CVE-2024-0001" }, references: new[] { new AdvisoryReference("https://example.com/advisory", "external", "vendor", summary: null, provenance: AdvisoryProvenance.Empty), new AdvisoryReference("https://example.com/blog", "article", "blog", summary: null, provenance: AdvisoryProvenance.Empty), }, affectedPackages: new[] { new AffectedPackage( type: AffectedPackageTypes.SemVer, identifier: "pkg:npm/sample@1.0.0", platform: null, versionRanges: new[] { new AffectedVersionRange("semver", "1.0.0", "1.2.0", null, null, AdvisoryProvenance.Empty), new AffectedVersionRange("semver", "1.2.0", null, null, null, AdvisoryProvenance.Empty), }, statuses: Array.Empty(), provenance: new[] { AdvisoryProvenance.Empty }) }, cvssMetrics: new[] { new CvssMetric("3.1", "AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", 9.8, "critical", AdvisoryProvenance.Empty) }, provenance: new[] { AdvisoryProvenance.Empty }); [Trait("Category", TestCategories.Unit)] [Fact] public void ComputeHash_ReturnsDeterministicValue() { var calculator = new CanonicalHashCalculator(); var first = calculator.ComputeHash(SampleAdvisory); var second = calculator.ComputeHash(SampleAdvisory); Assert.Equal(first, second); } [Trait("Category", TestCategories.Unit)] [Fact] public void ComputeHash_IgnoresOrderingDifferences() { var calculator = new CanonicalHashCalculator(); var reordered = new Advisory( SampleAdvisory.AdvisoryKey, SampleAdvisory.Title, SampleAdvisory.Summary, SampleAdvisory.Language, SampleAdvisory.Published, SampleAdvisory.Modified, SampleAdvisory.Severity, SampleAdvisory.ExploitKnown, aliases: SampleAdvisory.Aliases.Reverse().ToArray(), references: SampleAdvisory.References.Reverse().ToArray(), affectedPackages: SampleAdvisory.AffectedPackages.Reverse().ToArray(), cvssMetrics: SampleAdvisory.CvssMetrics.Reverse().ToArray(), provenance: SampleAdvisory.Provenance.Reverse().ToArray()); var originalHash = calculator.ComputeHash(SampleAdvisory); var reorderedHash = calculator.ComputeHash(reordered); Assert.Equal(originalHash, reorderedHash); } [Trait("Category", TestCategories.Unit)] [Fact] public void ComputeHash_NullReturnsEmpty() { var calculator = new CanonicalHashCalculator(); Assert.Empty(calculator.ComputeHash(null)); } }