// ----------------------------------------------------------------------------- // RichGraphBoundaryExtractorTests.cs // Sprint: SPRINT_3800_0002_0001_boundary_richgraph // Description: Unit tests for RichGraphBoundaryExtractor. // ----------------------------------------------------------------------------- using Microsoft.Extensions.Logging.Abstractions; using StellaOps.Scanner.Reachability.Boundary; using StellaOps.Scanner.Reachability.Gates; using Xunit; using StellaOps.TestKit; namespace StellaOps.Scanner.Reachability.Tests; public class RichGraphBoundaryExtractorTests { private readonly RichGraphBoundaryExtractor _extractor; public RichGraphBoundaryExtractorTests() { _extractor = new RichGraphBoundaryExtractor( NullLogger.Instance); } [Trait("Category", TestCategories.Unit)] [Fact] public void Extract_HttpRoot_ReturnsBoundaryWithApiSurface() { var root = new RichGraphRoot("root-http", "runtime", null); var rootNode = new RichGraphNode( Id: "node-1", SymbolId: "com.example.Controller.handle", CodeId: null, Purl: null, Lang: "java", Kind: "http_handler", Display: "POST /api/users", BuildId: null, Evidence: null, Attributes: null, SymbolDigest: null); var result = _extractor.Extract(root, rootNode, BoundaryExtractionContext.Empty); Assert.NotNull(result); Assert.Equal("network", result.Kind); Assert.NotNull(result.Surface); Assert.Equal("api", result.Surface.Type); Assert.Equal("https", result.Surface.Protocol); } [Trait("Category", TestCategories.Unit)] [Fact] public void Extract_GrpcRoot_ReturnsBoundaryWithGrpcProtocol() { var root = new RichGraphRoot("root-grpc", "runtime", null); var rootNode = new RichGraphNode( Id: "node-1", SymbolId: "com.example.UserService.getUser", CodeId: null, Purl: null, Lang: "java", Kind: "grpc_method", Display: "UserService.GetUser", BuildId: null, Evidence: null, Attributes: null, SymbolDigest: null); var result = _extractor.Extract(root, rootNode, BoundaryExtractionContext.Empty); Assert.NotNull(result); Assert.NotNull(result.Surface); Assert.Equal("grpc", result.Surface.Protocol); } [Trait("Category", TestCategories.Unit)] [Fact] public void Extract_CliRoot_ReturnsProcessBoundary() { var root = new RichGraphRoot("root-cli", "runtime", null); var rootNode = new RichGraphNode( Id: "node-1", SymbolId: "Main", CodeId: null, Purl: null, Lang: "csharp", Kind: "cli_command", Display: "stella scan", BuildId: null, Evidence: null, Attributes: null, SymbolDigest: null); var result = _extractor.Extract(root, rootNode, BoundaryExtractionContext.Empty); Assert.NotNull(result); Assert.Equal("process", result.Kind); Assert.NotNull(result.Surface); Assert.Equal("cli", result.Surface.Type); } [Trait("Category", TestCategories.Unit)] [Fact] public void Extract_LibraryPhase_ReturnsLibraryBoundary() { var root = new RichGraphRoot("root-lib", "library", null); var rootNode = new RichGraphNode( Id: "node-1", SymbolId: "Utils.parseJson", CodeId: null, Purl: null, Lang: "javascript", Kind: "function", Display: "parseJson", BuildId: null, Evidence: null, Attributes: null, SymbolDigest: null); var result = _extractor.Extract(root, rootNode, BoundaryExtractionContext.Empty); Assert.NotNull(result); Assert.Equal("library", result.Kind); Assert.NotNull(result.Surface); Assert.Equal("library", result.Surface.Type); } [Trait("Category", TestCategories.Unit)] [Fact] public void Extract_WithAuthGate_SetsAuthRequired() { var root = new RichGraphRoot("root-auth", "runtime", null); var rootNode = new RichGraphNode( Id: "node-1", SymbolId: "Controller.handle", CodeId: null, Purl: null, Lang: "java", Kind: "http_handler", Display: null, BuildId: null, Evidence: null, Attributes: null, SymbolDigest: null); var context = BoundaryExtractionContext.FromGates(new[] { new DetectedGate { Type = GateType.AuthRequired, Detail = "JWT token required", GuardSymbol = "AuthFilter.doFilter", Confidence = 0.9, DetectionMethod = "pattern_match" } }); var result = _extractor.Extract(root, rootNode, context); Assert.NotNull(result); Assert.NotNull(result.Auth); Assert.True(result.Auth.Required); Assert.Equal("jwt", result.Auth.Type); } [Trait("Category", TestCategories.Unit)] [Fact] public void Extract_WithAdminGate_SetsAdminRole() { var root = new RichGraphRoot("root-admin", "runtime", null); var rootNode = new RichGraphNode( Id: "node-1", SymbolId: "AdminController.handle", CodeId: null, Purl: null, Lang: "java", Kind: "http_handler", Display: null, BuildId: null, Evidence: null, Attributes: null, SymbolDigest: null); var context = BoundaryExtractionContext.FromGates(new[] { new DetectedGate { Type = GateType.AdminOnly, Detail = "Requires admin role", GuardSymbol = "RoleFilter.check", Confidence = 0.85, DetectionMethod = "annotation" } }); var result = _extractor.Extract(root, rootNode, context); Assert.NotNull(result); Assert.NotNull(result.Auth); Assert.True(result.Auth.Required); Assert.NotNull(result.Auth.Roles); Assert.Contains("admin", result.Auth.Roles); } [Trait("Category", TestCategories.Unit)] [Fact] public void Extract_WithFeatureFlagGate_AddsControl() { var root = new RichGraphRoot("root-ff", "runtime", null); var rootNode = new RichGraphNode( Id: "node-1", SymbolId: "BetaFeature.handle", CodeId: null, Purl: null, Lang: "java", Kind: "http_handler", Display: null, BuildId: null, Evidence: null, Attributes: null, SymbolDigest: null); var context = BoundaryExtractionContext.FromGates(new[] { new DetectedGate { Type = GateType.FeatureFlag, Detail = "beta_users_only", GuardSymbol = "FeatureFlags.isEnabled", Confidence = 0.95, DetectionMethod = "call_analysis" } }); var result = _extractor.Extract(root, rootNode, context); Assert.NotNull(result); Assert.NotNull(result.Controls); Assert.Single(result.Controls); Assert.Equal("feature_flag", result.Controls[0].Type); Assert.True(result.Controls[0].Active); } [Trait("Category", TestCategories.Unit)] [Fact] public void Extract_WithInternetFacingContext_SetsExposure() { var root = new RichGraphRoot("root-public", "runtime", null); var rootNode = new RichGraphNode( Id: "node-1", SymbolId: "PublicApi.handle", CodeId: null, Purl: null, Lang: "java", Kind: "http_handler", Display: null, BuildId: null, Evidence: null, Attributes: null, SymbolDigest: null); var context = BoundaryExtractionContext.ForEnvironment( "production", isInternetFacing: true, networkZone: "dmz"); var result = _extractor.Extract(root, rootNode, context); Assert.NotNull(result); Assert.NotNull(result.Exposure); Assert.True(result.Exposure.InternetFacing); Assert.Equal("dmz", result.Exposure.Zone); Assert.Equal("public", result.Exposure.Level); } [Trait("Category", TestCategories.Unit)] [Fact] public void Extract_InternalService_SetsInternalExposure() { var root = new RichGraphRoot("root-internal", "runtime", null); var rootNode = new RichGraphNode( Id: "node-1", SymbolId: "InternalService.process", CodeId: null, Purl: null, Lang: "java", Kind: "internal_handler", Display: null, BuildId: null, Evidence: null, Attributes: null, SymbolDigest: null); var result = _extractor.Extract(root, rootNode, BoundaryExtractionContext.Empty); Assert.NotNull(result); Assert.NotNull(result.Exposure); Assert.False(result.Exposure.InternetFacing); Assert.Equal("internal", result.Exposure.Level); } [Trait("Category", TestCategories.Unit)] [Fact] public void Extract_SetsConfidenceBasedOnContext() { var root = new RichGraphRoot("root-1", "runtime", null); var rootNode = new RichGraphNode( Id: "node-1", SymbolId: "Api.handle", CodeId: null, Purl: null, Lang: "java", Kind: "http_handler", Display: null, BuildId: null, Evidence: null, Attributes: null, SymbolDigest: null); // Empty context should have lower confidence var emptyResult = _extractor.Extract(root, rootNode, BoundaryExtractionContext.Empty); // Rich context should have higher confidence var richContext = new BoundaryExtractionContext { IsInternetFacing = true, NetworkZone = "dmz", DetectedGates = new[] { new DetectedGate { Type = GateType.AuthRequired, Detail = "auth", GuardSymbol = "auth", Confidence = 0.9, DetectionMethod = "test" } } }; var richResult = _extractor.Extract(root, rootNode, richContext); Assert.NotNull(emptyResult); Assert.NotNull(richResult); Assert.True(richResult.Confidence > emptyResult.Confidence); } [Trait("Category", TestCategories.Unit)] [Fact] public void Extract_IsDeterministic() { var root = new RichGraphRoot("root-det", "runtime", null); var rootNode = new RichGraphNode( Id: "node-1", SymbolId: "Api.handle", CodeId: null, Purl: null, Lang: "java", Kind: "http_handler", Display: "GET /api/test", BuildId: null, Evidence: null, Attributes: null, SymbolDigest: null); var context = BoundaryExtractionContext.FromGates(new[] { new DetectedGate { Type = GateType.AuthRequired, Detail = "JWT", GuardSymbol = "Auth", Confidence = 0.9, DetectionMethod = "test" } }); var result1 = _extractor.Extract(root, rootNode, context); var result2 = _extractor.Extract(root, rootNode, context); Assert.NotNull(result1); Assert.NotNull(result2); Assert.Equal(result1.Kind, result2.Kind); Assert.Equal(result1.Surface?.Type, result2.Surface?.Type); Assert.Equal(result1.Auth?.Required, result2.Auth?.Required); Assert.Equal(result1.Confidence, result2.Confidence); } [Trait("Category", TestCategories.Unit)] [Fact] public void CanHandle_AlwaysReturnsTrue() { Assert.True(_extractor.CanHandle(BoundaryExtractionContext.Empty)); Assert.True(_extractor.CanHandle(BoundaryExtractionContext.ForEnvironment("test"))); } [Trait("Category", TestCategories.Unit)] [Fact] public void Priority_ReturnsBaseValue() { Assert.Equal(100, _extractor.Priority); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task ExtractAsync_ReturnsResult() { var root = new RichGraphRoot("root-async", "runtime", null); var rootNode = new RichGraphNode( Id: "node-1", SymbolId: "Api.handle", CodeId: null, Purl: null, Lang: "java", Kind: "http_handler", Display: null, BuildId: null, Evidence: null, Attributes: null, SymbolDigest: null); var result = await _extractor.ExtractAsync(root, rootNode, BoundaryExtractionContext.Empty); Assert.NotNull(result); Assert.Equal("network", result.Kind); } }