Files
git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.Reachability.Tests/GatewayBoundaryExtractorTests.cs

968 lines
28 KiB
C#

// -----------------------------------------------------------------------------
// GatewayBoundaryExtractorTests.cs
// Sprint: SPRINT_3800_0002_0003_boundary_gateway
// Description: Unit tests for GatewayBoundaryExtractor.
// -----------------------------------------------------------------------------
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 GatewayBoundaryExtractorTests
{
private readonly GatewayBoundaryExtractor _extractor;
public GatewayBoundaryExtractorTests()
{
_extractor = new GatewayBoundaryExtractor(
NullLogger<GatewayBoundaryExtractor>.Instance);
}
#region Priority and CanHandle
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Priority_Returns250_HigherThanK8sExtractor()
{
Assert.Equal(250, _extractor.Priority);
}
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("gateway", true)]
[InlineData("kong", true)]
[InlineData("Kong", true)]
[InlineData("envoy", true)]
[InlineData("istio", true)]
[InlineData("apigateway", true)]
[InlineData("traefik", true)]
[InlineData("k8s", false)]
[InlineData("static", false)]
public void CanHandle_WithSource_ReturnsExpected(string source, bool expected)
{
var context = BoundaryExtractionContext.Empty with { Source = source };
Assert.Equal(expected, _extractor.CanHandle(context));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanHandle_WithKongAnnotations_ReturnsTrue()
{
var context = BoundaryExtractionContext.Empty with
{
Annotations = new Dictionary<string, string>
{
["kong.route.path"] = "/api"
}
};
Assert.True(_extractor.CanHandle(context));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanHandle_WithIstioAnnotations_ReturnsTrue()
{
var context = BoundaryExtractionContext.Empty with
{
Annotations = new Dictionary<string, string>
{
["istio.io/rev"] = "stable"
}
};
Assert.True(_extractor.CanHandle(context));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanHandle_WithTraefikAnnotations_ReturnsTrue()
{
var context = BoundaryExtractionContext.Empty with
{
Annotations = new Dictionary<string, string>
{
["traefik.http.routers.my-router.rule"] = "Host(`example.com`)"
}
};
Assert.True(_extractor.CanHandle(context));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CanHandle_WithEmptyAnnotations_ReturnsFalse()
{
var context = BoundaryExtractionContext.Empty;
Assert.False(_extractor.CanHandle(context));
}
#endregion
#region Gateway Type Detection
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithKongSource_ReturnsKongGatewaySource()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong"
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.Equal("gateway:kong", result.Source);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithEnvoySource_ReturnsEnvoyGatewaySource()
{
var root = new RichGraphRoot("root-1", "envoy", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "envoy"
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.Equal("gateway:envoy", result.Source);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithIstioAnnotations_ReturnsEnvoyGatewaySource()
{
var root = new RichGraphRoot("root-1", "gateway", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "gateway",
Annotations = new Dictionary<string, string>
{
["istio.io/rev"] = "stable"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.Equal("gateway:envoy", result.Source);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithApiGatewaySource_ReturnsAwsApigwSource()
{
var root = new RichGraphRoot("root-1", "apigateway", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "apigateway"
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.Equal("gateway:aws-apigw", result.Source);
}
#endregion
#region Exposure Detection
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_DefaultGateway_ReturnsPublicExposure()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong"
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Exposure);
Assert.Equal("public", result.Exposure.Level);
Assert.True(result.Exposure.InternetFacing);
Assert.True(result.Exposure.BehindProxy);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithInternalFlag_ReturnsInternalExposure()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.internal"] = "true"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Exposure);
Assert.Equal("internal", result.Exposure.Level);
Assert.False(result.Exposure.InternetFacing);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithIstioMesh_ReturnsInternalExposure()
{
var root = new RichGraphRoot("root-1", "envoy", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "envoy",
Annotations = new Dictionary<string, string>
{
["istio.io/mesh-config"] = "enabled"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Exposure);
Assert.Equal("internal", result.Exposure.Level);
Assert.False(result.Exposure.InternetFacing);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithAwsPrivateEndpoint_ReturnsInternalExposure()
{
var root = new RichGraphRoot("root-1", "apigateway", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "apigateway",
Annotations = new Dictionary<string, string>
{
["apigateway.endpoint-type"] = "PRIVATE"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Exposure);
Assert.Equal("internal", result.Exposure.Level);
Assert.False(result.Exposure.InternetFacing);
}
#endregion
#region Surface Detection
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithKongPath_ReturnsSurfaceWithPath()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.route.path"] = "/api/v1"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Surface);
Assert.Equal("/api/v1", result.Surface.Path);
Assert.Equal("api", result.Surface.Type);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithKongHost_ReturnsSurfaceWithHost()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.route.host"] = "api.example.com"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Surface);
Assert.Equal("api.example.com", result.Surface.Host);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithGrpcAnnotation_ReturnsGrpcProtocol()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.protocol.grpc"] = "true"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Surface);
Assert.Equal("grpc", result.Surface.Protocol);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithWebsocketAnnotation_ReturnsWssProtocol()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.upgrade.websocket"] = "true"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Surface);
Assert.Equal("wss", result.Surface.Protocol);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_DefaultProtocol_ReturnsHttps()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong"
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Surface);
Assert.Equal("https", result.Surface.Protocol);
Assert.Equal(443, result.Surface.Port);
}
#endregion
#region Kong Auth Detection
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithKongJwtPlugin_ReturnsJwtAuth()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.plugin.jwt"] = "enabled"
}
};
var result = _extractor.Extract(root, null, 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_WithKongKeyAuth_ReturnsApiKeyAuth()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.plugin.key-auth"] = "enabled"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Auth);
Assert.True(result.Auth.Required);
Assert.Equal("api_key", result.Auth.Type);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithKongAcl_ReturnsRoles()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.plugin.jwt"] = "enabled",
["kong.plugin.acl.allow"] = "admin,editor,viewer"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Auth);
Assert.NotNull(result.Auth.Roles);
Assert.Equal(3, result.Auth.Roles.Count);
Assert.Contains("admin", result.Auth.Roles);
}
#endregion
#region Envoy/Istio Auth Detection
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithIstioJwt_ReturnsJwtAuth()
{
var root = new RichGraphRoot("root-1", "envoy", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "envoy",
Annotations = new Dictionary<string, string>
{
["istio.io/requestauthentication.jwt"] = "enabled"
}
};
var result = _extractor.Extract(root, null, 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_WithIstioMtls_ReturnsMtlsAuth()
{
var root = new RichGraphRoot("root-1", "envoy", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "envoy",
Annotations = new Dictionary<string, string>
{
["istio.io/peerauthentication.mtls"] = "STRICT"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Auth);
Assert.True(result.Auth.Required);
Assert.Equal("mtls", result.Auth.Type);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithEnvoyOidc_ReturnsOAuth2Auth()
{
var root = new RichGraphRoot("root-1", "envoy", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "envoy",
Annotations = new Dictionary<string, string>
{
["envoy.filter.oidc.provider"] = "https://auth.example.com"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Auth);
Assert.True(result.Auth.Required);
Assert.Equal("oauth2", result.Auth.Type);
Assert.Equal("https://auth.example.com", result.Auth.Provider);
}
#endregion
#region AWS API Gateway Auth Detection
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithCognitoAuthorizer_ReturnsOAuth2Auth()
{
var root = new RichGraphRoot("root-1", "apigateway", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "apigateway",
Annotations = new Dictionary<string, string>
{
["apigateway.authorizer.cognito"] = "user-pool-id"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Auth);
Assert.True(result.Auth.Required);
Assert.Equal("oauth2", result.Auth.Type);
Assert.Equal("cognito", result.Auth.Provider);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithApiKeyRequired_ReturnsApiKeyAuth()
{
var root = new RichGraphRoot("root-1", "apigateway", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "apigateway",
Annotations = new Dictionary<string, string>
{
["apigateway.api-key-required"] = "true"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Auth);
Assert.True(result.Auth.Required);
Assert.Equal("api_key", result.Auth.Type);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithLambdaAuthorizer_ReturnsCustomAuth()
{
var root = new RichGraphRoot("root-1", "apigateway", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "apigateway",
Annotations = new Dictionary<string, string>
{
["apigateway.lambda-authorizer"] = "arn:aws:lambda:..."
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Auth);
Assert.True(result.Auth.Required);
Assert.Equal("custom", result.Auth.Type);
Assert.Equal("lambda", result.Auth.Provider);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithIamAuthorizer_ReturnsIamAuth()
{
var root = new RichGraphRoot("root-1", "apigateway", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "apigateway",
Annotations = new Dictionary<string, string>
{
["apigateway.iam-authorizer"] = "enabled"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Auth);
Assert.True(result.Auth.Required);
Assert.Equal("iam", result.Auth.Type);
Assert.Equal("aws-iam", result.Auth.Provider);
}
#endregion
#region Traefik Auth Detection
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithTraefikBasicAuth_ReturnsBasicAuth()
{
var root = new RichGraphRoot("root-1", "traefik", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "traefik",
Annotations = new Dictionary<string, string>
{
["traefik.http.middlewares.auth.basicauth.users"] = "admin:$$apr1$$..."
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Auth);
Assert.True(result.Auth.Required);
Assert.Equal("basic", result.Auth.Type);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithTraefikForwardAuth_ReturnsCustomAuth()
{
var root = new RichGraphRoot("root-1", "traefik", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "traefik",
Annotations = new Dictionary<string, string>
{
["traefik.http.middlewares.auth.forwardauth.address"] = "https://auth.example.com"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Auth);
Assert.True(result.Auth.Required);
Assert.Equal("custom", result.Auth.Type);
Assert.Equal("https://auth.example.com", result.Auth.Provider);
}
#endregion
#region Controls Detection
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithRateLimit_ReturnsRateLimitControl()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.plugin.rate-limiting"] = "100"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Controls);
Assert.Contains(result.Controls, c => c.Type == "rate_limit");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithIpRestriction_ReturnsIpAllowlistControl()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.plugin.ip-restriction.whitelist"] = "10.0.0.0/8"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Controls);
Assert.Contains(result.Controls, c => c.Type == "ip_allowlist");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithCors_ReturnsCorsControl()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.plugin.cors.origins"] = "https://example.com"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Controls);
Assert.Contains(result.Controls, c => c.Type == "cors");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithWaf_ReturnsWafControl()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.plugin.bot-detection"] = "enabled"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Controls);
Assert.Contains(result.Controls, c => c.Type == "waf");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithRequestValidation_ReturnsInputValidationControl()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.plugin.request-validation"] = "enabled"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Controls);
Assert.Contains(result.Controls, c => c.Type == "input_validation");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithMultipleControls_ReturnsAllControls()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.plugin.rate-limiting"] = "100",
["kong.plugin.cors.origins"] = "https://example.com",
["kong.plugin.bot-detection"] = "enabled"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.NotNull(result.Controls);
Assert.Equal(3, result.Controls.Count);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithNoControls_ReturnsNullControls()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong"
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.Null(result.Controls);
}
#endregion
#region Confidence and Metadata
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_BaseConfidence_Returns0Point75()
{
var root = new RichGraphRoot("root-1", "gateway", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "gateway"
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.Equal(0.75, result.Confidence, precision: 2);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithKnownGateway_IncreasesConfidence()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong"
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.Equal(0.85, result.Confidence, precision: 2);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithAuthAndRouteInfo_MaximizesConfidence()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.plugin.jwt"] = "enabled",
["kong.route.path"] = "/api/v1"
}
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.Equal(0.95, result.Confidence, precision: 2);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_ReturnsNetworkKind()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong"
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.Equal("network", result.Kind);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_BuildsEvidenceRef_WithGatewayType()
{
var root = new RichGraphRoot("root-123", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Namespace = "production",
EnvironmentId = "env-456"
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.Equal("gateway/kong/production/env-456/root-123", result.EvidenceRef);
}
#endregion
#region ExtractAsync
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExtractAsync_ReturnsSameResultAsExtract()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong",
Annotations = new Dictionary<string, string>
{
["kong.plugin.jwt"] = "enabled"
}
};
var syncResult = _extractor.Extract(root, null, context);
var asyncResult = await _extractor.ExtractAsync(root, null, context);
Assert.NotNull(syncResult);
Assert.NotNull(asyncResult);
Assert.Equal(syncResult.Kind, asyncResult.Kind);
Assert.Equal(syncResult.Source, asyncResult.Source);
Assert.Equal(syncResult.Confidence, asyncResult.Confidence);
}
#endregion
#region Edge Cases
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithNullRoot_ThrowsArgumentNullException()
{
var context = BoundaryExtractionContext.Empty with { Source = "kong" };
Assert.Throws<ArgumentNullException>(() => _extractor.Extract(null!, null, context));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WhenCannotHandle_ReturnsNull()
{
var root = new RichGraphRoot("root-1", "static", null);
var context = BoundaryExtractionContext.Empty with { Source = "static" };
var result = _extractor.Extract(root, null, context);
Assert.Null(result);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Extract_WithNoAuth_ReturnsNullAuth()
{
var root = new RichGraphRoot("root-1", "kong", null);
var context = BoundaryExtractionContext.Empty with
{
Source = "kong"
};
var result = _extractor.Extract(root, null, context);
Assert.NotNull(result);
Assert.Null(result.Auth);
}
#endregion
}