/** * Graph Validation & NFC Tests * Sprint: SPRINT_9100_0003_0002 (Graph Validation & NFC Normalization) * Tasks: VALID-9100-021 through VALID-9100-028 */ using Xunit; namespace StellaOps.Resolver.Tests; public class GraphValidationTests { [Fact] public void NfcNormalization_ProducesConsistentNodeIds() { // VALID-9100-021: NFC normalization produces consistent NodeIds // Using different Unicode representations of the same character // é can be represented as: // - U+00E9 (precomposed: LATIN SMALL LETTER E WITH ACUTE) // - U+0065 U+0301 (decomposed: e + COMBINING ACUTE ACCENT) var precomposed = "caf\u00E9"; // café with precomposed é var decomposed = "cafe\u0301"; // café with decomposed é var nodeId1 = NodeId.From("package", precomposed); var nodeId2 = NodeId.From("package", decomposed); // After NFC normalization, both should produce the same NodeId Assert.Equal(nodeId1, nodeId2); } [Fact] public void EdgeReferencingNonExistentNode_Detected() { // VALID-9100-022 var node1 = Node.Create("package", "a"); var nonExistentNodeId = NodeId.From("package", "nonexistent"); var edge = Edge.Create(node1.Id, "depends_on", nonExistentNodeId); var graph = EvidenceGraph.Create(new[] { node1 }, new[] { edge }); var detector = new DefaultImplicitDataDetector(); var violations = detector.Detect(graph); Assert.Contains(violations, v => v.ViolationType == "DanglingEdgeDestination"); } [Fact] public void DuplicateNodeIds_Detected() { // VALID-9100-023 var node1 = Node.Create("package", "a"); var node2 = new Node(node1.Id, "package", "a-duplicate"); // Same ID, different key var graph = new EvidenceGraph { Nodes = [node1, node2], Edges = [] }; var detector = new DefaultImplicitDataDetector(); var violations = detector.Detect(graph); Assert.Contains(violations, v => v.ViolationType == "DuplicateNodeId"); } [Fact] public void DuplicateEdgeIds_Detected() { // VALID-9100-024 var node1 = Node.Create("package", "a"); var node2 = Node.Create("package", "b"); var edge1 = Edge.Create(node1.Id, "depends_on", node2.Id); var edge2 = Edge.Create(node1.Id, "depends_on", node2.Id); // Same EdgeId var graph = new EvidenceGraph { Nodes = [node1, node2], Edges = [edge1, edge2] }; var detector = new DefaultImplicitDataDetector(); var violations = detector.Detect(graph); Assert.Contains(violations, v => v.ViolationType == "DuplicateEdgeId"); } [Fact] public void ValidGraph_PassesAllChecks() { // VALID-9100-027 var node1 = Node.Create("package", "a"); var node2 = Node.Create("package", "b"); var node3 = Node.Create("package", "c"); var edge1 = Edge.Create(node1.Id, "depends_on", node2.Id); var edge2 = Edge.Create(node2.Id, "depends_on", node3.Id); var graph = EvidenceGraph.Create(new[] { node1, node2, node3 }, new[] { edge1, edge2 }); var validator = new DefaultGraphValidator(); var result = validator.Validate(graph); Assert.True(result.IsValid); Assert.Empty(result.Errors); } [Fact] public void NfcNormalization_IsIdempotent() { // VALID-9100-028: Property test - NFC is idempotent var normalizer = NfcStringNormalizer.Instance; var input = "café"; var normalized1 = normalizer.Normalize(input); var normalized2 = normalizer.Normalize(normalized1); var normalized3 = normalizer.Normalize(normalized2); Assert.Equal(normalized1, normalized2); Assert.Equal(normalized2, normalized3); } [Fact] public void EmptyGraph_IsValid() { var graph = EvidenceGraph.Empty; var validator = new DefaultGraphValidator(); var result = validator.Validate(graph); Assert.True(result.IsValid); } }