// Licensed to StellaOps under the AGPL-3.0-or-later license. using System.Collections.Immutable; using StellaOps.Scanner.EntryTrace.Mesh; using StellaOps.Scanner.EntryTrace.Semantic; using Xunit; namespace StellaOps.Scanner.EntryTrace.Tests.Mesh; /// /// Unit tests for and related records. /// public sealed class MeshEntrypointGraphTests { [Fact] public void FindPath_DirectConnection_ReturnsPath() { // Arrange var frontend = CreateServiceNode("frontend"); var backend = CreateServiceNode("backend"); var edge = new CrossContainerEdge { FromServiceId = "frontend", ToServiceId = "backend", Port = 8080, Protocol = "HTTP" }; var graph = new MeshEntrypointGraph { MeshId = "test-mesh", Services = ImmutableArray.Create(frontend, backend), Edges = ImmutableArray.Create(edge), IngressPaths = ImmutableArray.Empty, Type = MeshType.Kubernetes, AnalyzedAt = "2025-12-20T12:00:00Z" }; // Act var path = graph.FindPath("frontend", "backend"); // Assert Assert.NotNull(path); Assert.Equal("frontend", path.Source.ServiceId); Assert.Equal("backend", path.Target.ServiceId); Assert.Single(path.Hops); Assert.Equal(1, path.HopCount); } [Fact] public void FindPath_MultiHop_ReturnsShortestPath() { // Arrange var api = CreateServiceNode("api"); var cache = CreateServiceNode("cache"); var db = CreateServiceNode("db"); var apiToCache = new CrossContainerEdge { FromServiceId = "api", ToServiceId = "cache", Port = 6379, Protocol = "TCP" }; var cacheToDb = new CrossContainerEdge { FromServiceId = "cache", ToServiceId = "db", Port = 5432, Protocol = "TCP" }; var graph = new MeshEntrypointGraph { MeshId = "test-mesh", Services = ImmutableArray.Create(api, cache, db), Edges = ImmutableArray.Create(apiToCache, cacheToDb), IngressPaths = ImmutableArray.Empty, Type = MeshType.Kubernetes, AnalyzedAt = "2025-12-20T12:00:00Z" }; // Act var path = graph.FindPath("api", "db"); // Assert Assert.NotNull(path); Assert.Equal(2, path.HopCount); Assert.Equal("api", path.Source.ServiceId); Assert.Equal("db", path.Target.ServiceId); } [Fact] public void FindPath_NoConnection_ReturnsNull() { // Arrange var frontend = CreateServiceNode("frontend"); var isolated = CreateServiceNode("isolated"); var graph = new MeshEntrypointGraph { MeshId = "test-mesh", Services = ImmutableArray.Create(frontend, isolated), Edges = ImmutableArray.Empty, IngressPaths = ImmutableArray.Empty, Type = MeshType.Kubernetes, AnalyzedAt = "2025-12-20T12:00:00Z" }; // Act var path = graph.FindPath("frontend", "isolated"); // Assert Assert.Null(path); } [Fact] public void FindPath_SameService_ReturnsNull() { // Arrange var frontend = CreateServiceNode("frontend"); var graph = new MeshEntrypointGraph { MeshId = "test-mesh", Services = ImmutableArray.Create(frontend), Edges = ImmutableArray.Empty, IngressPaths = ImmutableArray.Empty, Type = MeshType.Kubernetes, AnalyzedAt = "2025-12-20T12:00:00Z" }; // Act var path = graph.FindPath("frontend", "frontend"); // Assert Assert.Null(path); } [Fact] public void FindPath_ServiceNotFound_ReturnsNull() { // Arrange var frontend = CreateServiceNode("frontend"); var graph = new MeshEntrypointGraph { MeshId = "test-mesh", Services = ImmutableArray.Create(frontend), Edges = ImmutableArray.Empty, IngressPaths = ImmutableArray.Empty, Type = MeshType.Kubernetes, AnalyzedAt = "2025-12-20T12:00:00Z" }; // Act var path = graph.FindPath("frontend", "nonexistent"); // Assert Assert.Null(path); } [Fact] public void FindPathsToService_WithIngress_ReturnsIngressPaths() { // Arrange var frontend = CreateServiceNode("frontend"); var backend = CreateServiceNode("backend"); var edge = new CrossContainerEdge { FromServiceId = "frontend", ToServiceId = "backend", Port = 8080, Protocol = "HTTP" }; var ingress = new IngressPath { IngressName = "main-ingress", Host = "api.example.com", Path = "/api/*", TargetServiceId = "frontend", TargetPort = 80 }; var graph = new MeshEntrypointGraph { MeshId = "test-mesh", Services = ImmutableArray.Create(frontend, backend), Edges = ImmutableArray.Create(edge), IngressPaths = ImmutableArray.Create(ingress), Type = MeshType.Kubernetes, AnalyzedAt = "2025-12-20T12:00:00Z" }; // Act var paths = graph.FindPathsToService("backend"); // Assert Assert.NotEmpty(paths); Assert.True(paths[0].IsIngressExposed); Assert.NotNull(paths[0].IngressPath); Assert.Equal("api.example.com", paths[0].IngressPath.Host); } [Fact] public void FindPathsToService_NoIngress_ReturnsEmpty() { // Arrange var frontend = CreateServiceNode("frontend"); var backend = CreateServiceNode("backend"); var edge = new CrossContainerEdge { FromServiceId = "frontend", ToServiceId = "backend", Port = 8080, Protocol = "HTTP" }; var graph = new MeshEntrypointGraph { MeshId = "test-mesh", Services = ImmutableArray.Create(frontend, backend), Edges = ImmutableArray.Create(edge), IngressPaths = ImmutableArray.Empty, Type = MeshType.Kubernetes, AnalyzedAt = "2025-12-20T12:00:00Z" }; // Act var paths = graph.FindPathsToService("backend"); // Assert Assert.Empty(paths); } private static ServiceNode CreateServiceNode(string serviceId) { return new ServiceNode { ServiceId = serviceId, ContainerName = serviceId, ImageDigest = "sha256:" + serviceId, Entrypoints = ImmutableArray.Empty, ExposedPorts = ImmutableArray.Create(8080) }; } } /// /// Unit tests for . /// public sealed class ServiceNodeTests { [Fact] public void InternalDns_DefaultsToEmpty() { // Arrange var node = new ServiceNode { ServiceId = "myapp", ContainerName = "myapp", ImageDigest = "sha256:abc", Entrypoints = ImmutableArray.Empty, ExposedPorts = ImmutableArray.Empty }; // Assert Assert.Empty(node.InternalDns); } [Fact] public void VulnerableComponents_DefaultsToEmpty() { // Arrange var node = new ServiceNode { ServiceId = "myapp", ContainerName = "myapp", ImageDigest = "sha256:abc", Entrypoints = ImmutableArray.Empty, ExposedPorts = ImmutableArray.Empty }; // Assert Assert.Empty(node.VulnerableComponents); } [Fact] public void Replicas_DefaultsToOne() { // Arrange var node = new ServiceNode { ServiceId = "myapp", ContainerName = "myapp", ImageDigest = "sha256:abc", Entrypoints = ImmutableArray.Empty, ExposedPorts = ImmutableArray.Empty }; // Assert Assert.Equal(1, node.Replicas); } [Fact] public void IsSidecar_DefaultsToFalse() { // Arrange var node = new ServiceNode { ServiceId = "myapp", ContainerName = "myapp", ImageDigest = "sha256:abc", Entrypoints = ImmutableArray.Empty, ExposedPorts = ImmutableArray.Empty }; // Assert Assert.False(node.IsSidecar); } } /// /// Unit tests for . /// public sealed class CrossContainerEdgeTests { [Fact] public void Confidence_DefaultsToOne() { // Arrange var edge = new CrossContainerEdge { FromServiceId = "frontend", ToServiceId = "backend", Port = 8080, Protocol = "HTTP" }; // Assert Assert.Equal(1.0f, edge.Confidence); } [Fact] public void Source_DefaultsToManifest() { // Arrange var edge = new CrossContainerEdge { FromServiceId = "frontend", ToServiceId = "backend", Port = 8080, Protocol = "HTTP" }; // Assert Assert.Equal(EdgeSource.Manifest, edge.Source); } [Fact] public void IsExternal_DefaultsToFalse() { // Arrange var edge = new CrossContainerEdge { FromServiceId = "frontend", ToServiceId = "backend", Port = 8080, Protocol = "HTTP" }; // Assert Assert.False(edge.IsExternal); } } /// /// Unit tests for . /// public sealed class CrossContainerPathTests { [Fact] public void GetAllVulnerableComponents_CombinesSourceAndTarget() { // Arrange var source = new ServiceNode { ServiceId = "frontend", ContainerName = "frontend", ImageDigest = "sha256:aaa", Entrypoints = ImmutableArray.Empty, ExposedPorts = ImmutableArray.Empty, VulnerableComponents = ImmutableArray.Create("pkg:npm/lodash@4.17.20") }; var target = new ServiceNode { ServiceId = "backend", ContainerName = "backend", ImageDigest = "sha256:bbb", Entrypoints = ImmutableArray.Empty, ExposedPorts = ImmutableArray.Empty, VulnerableComponents = ImmutableArray.Create("pkg:maven/log4j/log4j-core@2.14.1") }; var path = new CrossContainerPath { Source = source, Target = target, Hops = ImmutableArray.Empty, HopCount = 0, IsIngressExposed = false, ReachabilityConfidence = 1.0f }; // Act var allVulns = path.GetAllVulnerableComponents(); // Assert Assert.Equal(2, allVulns.Length); Assert.Contains("pkg:npm/lodash@4.17.20", allVulns); Assert.Contains("pkg:maven/log4j/log4j-core@2.14.1", allVulns); } [Fact] public void GetAllVulnerableComponents_DeduplicatesComponents() { // Arrange var sharedVuln = "pkg:npm/lodash@4.17.20"; var source = new ServiceNode { ServiceId = "frontend", ContainerName = "frontend", ImageDigest = "sha256:aaa", Entrypoints = ImmutableArray.Empty, ExposedPorts = ImmutableArray.Empty, VulnerableComponents = ImmutableArray.Create(sharedVuln) }; var target = new ServiceNode { ServiceId = "backend", ContainerName = "backend", ImageDigest = "sha256:bbb", Entrypoints = ImmutableArray.Empty, ExposedPorts = ImmutableArray.Empty, VulnerableComponents = ImmutableArray.Create(sharedVuln) }; var path = new CrossContainerPath { Source = source, Target = target, Hops = ImmutableArray.Empty, HopCount = 0, IsIngressExposed = false, ReachabilityConfidence = 1.0f }; // Act var allVulns = path.GetAllVulnerableComponents(); // Assert Assert.Single(allVulns); } [Fact] public void VulnerableComponents_DefaultsToEmpty() { // Arrange var source = new ServiceNode { ServiceId = "frontend", ContainerName = "frontend", ImageDigest = "sha256:aaa", Entrypoints = ImmutableArray.Empty, ExposedPorts = ImmutableArray.Empty }; var target = new ServiceNode { ServiceId = "backend", ContainerName = "backend", ImageDigest = "sha256:bbb", Entrypoints = ImmutableArray.Empty, ExposedPorts = ImmutableArray.Empty }; var path = new CrossContainerPath { Source = source, Target = target, Hops = ImmutableArray.Empty, HopCount = 0, IsIngressExposed = false, ReachabilityConfidence = 1.0f }; // Assert Assert.Empty(path.VulnerableComponents); } } /// /// Unit tests for . /// public sealed class IngressPathTests { [Fact] public void TlsEnabled_DefaultsToFalse() { // Arrange var ingress = new IngressPath { IngressName = "main-ingress", Host = "api.example.com", Path = "/api/*", TargetServiceId = "backend", TargetPort = 8080 }; // Assert Assert.False(ingress.TlsEnabled); } [Fact] public void TlsSecretName_IsNull_WhenTlsDisabled() { // Arrange var ingress = new IngressPath { IngressName = "main-ingress", Host = "api.example.com", Path = "/api/*", TargetServiceId = "backend", TargetPort = 8080 }; // Assert Assert.Null(ingress.TlsSecretName); } [Fact] public void CanHaveAnnotations() { // Arrange var ingress = new IngressPath { IngressName = "main-ingress", Host = "api.example.com", Path = "/api/*", TargetServiceId = "backend", TargetPort = 8080, Annotations = ImmutableDictionary.Empty .Add("nginx.ingress.kubernetes.io/rewrite-target", "/") }; // Assert Assert.NotNull(ingress.Annotations); Assert.Contains("nginx.ingress.kubernetes.io/rewrite-target", ingress.Annotations.Keys); } }