// 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);
}
}