using System.Collections.Immutable; using StellaOps.Graph.Indexer.Schema; namespace StellaOps.Graph.Indexer.Tests; public sealed class GraphIdentityTests { [Fact] public void ComputeNodeId_IsDeterministic_WhenTupleOrderChanges() { var tupleA = ImmutableDictionary.Empty .Add("purl", "pkg:npm/test@1.0.0") .Add("reason", "declared") .Add("scope", "runtime"); var tupleB = ImmutableDictionary.Empty .Add("scope", "runtime") .Add("reason", "declared") .Add("purl", "pkg:npm/test@1.0.0"); var idA = GraphIdentity.ComputeNodeId("Tenant-A", "Component", tupleA); var idB = GraphIdentity.ComputeNodeId("tenant-a", "component", tupleB); Assert.Equal(idA, idB); Assert.StartsWith("gn:tenant-a:component:", idA); } [Fact] public void ComputeEdgeId_IsCaseInsensitiveExceptFingerprintFields() { var tupleLower = ImmutableDictionary.Empty .Add("digest", "sha256:ABC") .Add("source", "sbom"); var tupleUpper = ImmutableDictionary.Empty .Add("digest", "sha256:abc") .Add("source", "SBOM"); var edgeA = GraphIdentity.ComputeEdgeId("TENANT", "depends_on", tupleLower); var edgeB = GraphIdentity.ComputeEdgeId("tenant", "DEPENDS_ON", tupleUpper); // digest key is case-sensitive by design; different casing produces different id when value changes. Assert.NotEqual(edgeA, edgeB); Assert.StartsWith("ge:tenant:DEPENDS_ON:", edgeA); } }