Add comprehensive tests for PathConfidenceScorer, PathEnumerator, ShellSymbolicExecutor, and SymbolicState
- Implemented unit tests for PathConfidenceScorer to evaluate path scoring under various conditions, including empty constraints, known and unknown constraints, environmental dependencies, and custom weights. - Developed tests for PathEnumerator to ensure correct path enumeration from simple scripts, handling known environments, and respecting maximum paths and depth limits. - Created tests for ShellSymbolicExecutor to validate execution of shell scripts, including handling of commands, branching, and environment tracking. - Added tests for SymbolicState to verify state management, variable handling, constraint addition, and environment dependency collection.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
// 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;
|
||||
@@ -6,366 +8,234 @@ using Xunit;
|
||||
namespace StellaOps.Scanner.EntryTrace.Tests.Mesh;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for MeshEntrypointGraph and related types.
|
||||
/// Part of Sprint 0412 - Task TEST-002.
|
||||
/// Unit tests for <see cref="MeshEntrypointGraph"/> and related records.
|
||||
/// </summary>
|
||||
public sealed class MeshEntrypointGraphTests
|
||||
{
|
||||
[Fact]
|
||||
public void MeshEntrypointGraph_Creation_SetsProperties()
|
||||
public void FindPath_DirectConnection_ReturnsPath()
|
||||
{
|
||||
// Arrange & Act
|
||||
// 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<IngressPath>.Empty,
|
||||
Type = MeshType.Kubernetes,
|
||||
Namespace = "default",
|
||||
Services = CreateServiceNodes(3),
|
||||
Edges = [],
|
||||
IngressPaths = [],
|
||||
AnalyzedAt = DateTime.UtcNow.ToString("O")
|
||||
AnalyzedAt = "2025-12-20T12:00:00Z"
|
||||
};
|
||||
|
||||
// Act
|
||||
var path = graph.FindPath("frontend", "backend");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test-mesh", graph.MeshId);
|
||||
Assert.Equal(MeshType.Kubernetes, graph.Type);
|
||||
Assert.Equal("default", graph.Namespace);
|
||||
Assert.Equal(3, graph.Services.Length);
|
||||
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 MeshEntrypointGraph_FindPathsToService_FindsDirectPath()
|
||||
public void FindPath_MultiHop_ReturnsShortestPath()
|
||||
{
|
||||
// Arrange
|
||||
var services = CreateServiceNodes(3);
|
||||
var edges = new[]
|
||||
{
|
||||
new CrossContainerEdge
|
||||
{
|
||||
EdgeId = "a->b",
|
||||
SourceServiceId = "svc-0",
|
||||
TargetServiceId = "svc-1",
|
||||
TargetPort = 8080
|
||||
},
|
||||
new CrossContainerEdge
|
||||
{
|
||||
EdgeId = "b->c",
|
||||
SourceServiceId = "svc-1",
|
||||
TargetServiceId = "svc-2",
|
||||
TargetPort = 8080
|
||||
}
|
||||
}.ToImmutableArray();
|
||||
var api = CreateServiceNode("api");
|
||||
var cache = CreateServiceNode("cache");
|
||||
var db = CreateServiceNode("db");
|
||||
|
||||
var ingressPaths = new[]
|
||||
var apiToCache = new CrossContainerEdge
|
||||
{
|
||||
new IngressPath
|
||||
{
|
||||
IngressName = "main-ingress",
|
||||
Host = "example.com",
|
||||
Path = "/",
|
||||
TargetServiceId = "svc-0",
|
||||
TargetPort = 8080
|
||||
}
|
||||
}.ToImmutableArray();
|
||||
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",
|
||||
MeshId = "test-mesh",
|
||||
Services = ImmutableArray.Create(api, cache, db),
|
||||
Edges = ImmutableArray.Create(apiToCache, cacheToDb),
|
||||
IngressPaths = ImmutableArray<IngressPath>.Empty,
|
||||
Type = MeshType.Kubernetes,
|
||||
Services = services,
|
||||
Edges = edges,
|
||||
IngressPaths = ingressPaths,
|
||||
AnalyzedAt = DateTime.UtcNow.ToString("O")
|
||||
AnalyzedAt = "2025-12-20T12:00:00Z"
|
||||
};
|
||||
|
||||
// Act
|
||||
var paths = graph.FindPathsToService("svc-2", maxDepth: 5);
|
||||
var path = graph.FindPath("api", "db");
|
||||
|
||||
// Assert
|
||||
Assert.Single(paths);
|
||||
Assert.Equal(2, paths[0].Hops.Length);
|
||||
Assert.True(paths[0].IsExternallyExposed);
|
||||
Assert.NotNull(path);
|
||||
Assert.Equal(2, path.HopCount);
|
||||
Assert.Equal("api", path.Source.ServiceId);
|
||||
Assert.Equal("db", path.Target.ServiceId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeshEntrypointGraph_FindPathsToService_RespectsMaxDepth()
|
||||
public void FindPath_NoConnection_ReturnsNull()
|
||||
{
|
||||
// Arrange - Long chain of services
|
||||
var services = CreateServiceNodes(10);
|
||||
var edges = new List<CrossContainerEdge>();
|
||||
for (var i = 0; i < 9; i++)
|
||||
{
|
||||
edges.Add(new CrossContainerEdge
|
||||
{
|
||||
EdgeId = $"svc-{i}->svc-{i + 1}",
|
||||
SourceServiceId = $"svc-{i}",
|
||||
TargetServiceId = $"svc-{i + 1}",
|
||||
TargetPort = 8080
|
||||
});
|
||||
}
|
||||
// Arrange
|
||||
var frontend = CreateServiceNode("frontend");
|
||||
var isolated = CreateServiceNode("isolated");
|
||||
|
||||
var graph = new MeshEntrypointGraph
|
||||
{
|
||||
MeshId = "test",
|
||||
MeshId = "test-mesh",
|
||||
Services = ImmutableArray.Create(frontend, isolated),
|
||||
Edges = ImmutableArray<CrossContainerEdge>.Empty,
|
||||
IngressPaths = ImmutableArray<IngressPath>.Empty,
|
||||
Type = MeshType.Kubernetes,
|
||||
Services = services,
|
||||
Edges = edges.ToImmutableArray(),
|
||||
IngressPaths = [],
|
||||
AnalyzedAt = DateTime.UtcNow.ToString("O")
|
||||
};
|
||||
|
||||
// Act - Limit depth to 3
|
||||
var paths = graph.FindPathsToService("svc-9", maxDepth: 3);
|
||||
|
||||
// Assert - Should not find path since it requires 9 hops
|
||||
Assert.Empty(paths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeshEntrypointGraph_FindPathsToService_NoPathExists()
|
||||
{
|
||||
// Arrange - Disconnected services
|
||||
var services = CreateServiceNodes(2);
|
||||
var graph = new MeshEntrypointGraph
|
||||
{
|
||||
MeshId = "test",
|
||||
Type = MeshType.Kubernetes,
|
||||
Services = services,
|
||||
Edges = [],
|
||||
IngressPaths = [],
|
||||
AnalyzedAt = DateTime.UtcNow.ToString("O")
|
||||
AnalyzedAt = "2025-12-20T12:00:00Z"
|
||||
};
|
||||
|
||||
// Act
|
||||
var paths = graph.FindPathsToService("svc-1", maxDepth: 5);
|
||||
var path = graph.FindPath("frontend", "isolated");
|
||||
|
||||
// Assert
|
||||
Assert.Empty(paths);
|
||||
Assert.Null(path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServiceNode_Creation_SetsProperties()
|
||||
public void FindPath_SameService_ReturnsNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
var node = new ServiceNode
|
||||
// Arrange
|
||||
var frontend = CreateServiceNode("frontend");
|
||||
|
||||
var graph = new MeshEntrypointGraph
|
||||
{
|
||||
ServiceId = "my-service",
|
||||
ContainerName = "app",
|
||||
ImageDigest = "sha256:abc123",
|
||||
ImageReference = "myapp:v1.0.0",
|
||||
Entrypoints = [],
|
||||
ExposedPorts = [8080, 8443],
|
||||
InternalDns = ["my-service.default.svc.cluster.local"],
|
||||
Labels = new Dictionary<string, string> { ["app"] = "my-app" }.ToImmutableDictionary(),
|
||||
Replicas = 3
|
||||
MeshId = "test-mesh",
|
||||
Services = ImmutableArray.Create(frontend),
|
||||
Edges = ImmutableArray<CrossContainerEdge>.Empty,
|
||||
IngressPaths = ImmutableArray<IngressPath>.Empty,
|
||||
Type = MeshType.Kubernetes,
|
||||
AnalyzedAt = "2025-12-20T12:00:00Z"
|
||||
};
|
||||
|
||||
// Act
|
||||
var path = graph.FindPath("frontend", "frontend");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("my-service", node.ServiceId);
|
||||
Assert.Equal("app", node.ContainerName);
|
||||
Assert.Equal(2, node.ExposedPorts.Length);
|
||||
Assert.Equal(3, node.Replicas);
|
||||
Assert.Null(path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CrossContainerEdge_Creation_SetsProperties()
|
||||
public void FindPath_ServiceNotFound_ReturnsNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
// Arrange
|
||||
var frontend = CreateServiceNode("frontend");
|
||||
|
||||
var graph = new MeshEntrypointGraph
|
||||
{
|
||||
MeshId = "test-mesh",
|
||||
Services = ImmutableArray.Create(frontend),
|
||||
Edges = ImmutableArray<CrossContainerEdge>.Empty,
|
||||
IngressPaths = ImmutableArray<IngressPath>.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
|
||||
{
|
||||
EdgeId = "frontend->backend",
|
||||
SourceServiceId = "frontend",
|
||||
TargetServiceId = "backend",
|
||||
SourcePort = 0,
|
||||
TargetPort = 8080,
|
||||
Protocol = "http",
|
||||
IsExplicit = true
|
||||
FromServiceId = "frontend",
|
||||
ToServiceId = "backend",
|
||||
Port = 8080,
|
||||
Protocol = "HTTP"
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.Equal("frontend->backend", edge.EdgeId);
|
||||
Assert.Equal("frontend", edge.SourceServiceId);
|
||||
Assert.Equal("backend", edge.TargetServiceId);
|
||||
Assert.Equal(8080, edge.TargetPort);
|
||||
Assert.True(edge.IsExplicit);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CrossContainerPath_TracksHops()
|
||||
{
|
||||
// Arrange
|
||||
var hops = new[]
|
||||
{
|
||||
new CrossContainerEdge
|
||||
{
|
||||
EdgeId = "a->b",
|
||||
SourceServiceId = "a",
|
||||
TargetServiceId = "b",
|
||||
TargetPort = 8080
|
||||
},
|
||||
new CrossContainerEdge
|
||||
{
|
||||
EdgeId = "b->c",
|
||||
SourceServiceId = "b",
|
||||
TargetServiceId = "c",
|
||||
TargetPort = 9090
|
||||
}
|
||||
}.ToImmutableArray();
|
||||
|
||||
// Act
|
||||
var path = new CrossContainerPath
|
||||
{
|
||||
PathId = "path-1",
|
||||
SourceServiceId = "a",
|
||||
TargetServiceId = "c",
|
||||
Hops = hops,
|
||||
IsExternallyExposed = true,
|
||||
VulnerableComponents = ["pkg:npm/lodash@4.17.20"],
|
||||
TotalLatencyEstimateMs = 10
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, path.Hops.Length);
|
||||
Assert.True(path.IsExternallyExposed);
|
||||
Assert.Single(path.VulnerableComponents);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IngressPath_TracksExternalExposure()
|
||||
{
|
||||
// Arrange & Act
|
||||
var ingress = new IngressPath
|
||||
{
|
||||
IngressName = "main-ingress",
|
||||
Host = "api.example.com",
|
||||
Path = "/v1",
|
||||
TargetServiceId = "api-gateway",
|
||||
TargetPort = 8080,
|
||||
TlsEnabled = true,
|
||||
TlsSecretName = "api-tls-secret",
|
||||
Annotations = new Dictionary<string, string>
|
||||
{
|
||||
["nginx.ingress.kubernetes.io/rewrite-target"] = "/"
|
||||
}.ToImmutableDictionary()
|
||||
Path = "/api/*",
|
||||
TargetServiceId = "frontend",
|
||||
TargetPort = 80
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.Equal("main-ingress", ingress.IngressName);
|
||||
Assert.Equal("api.example.com", ingress.Host);
|
||||
Assert.True(ingress.TlsEnabled);
|
||||
Assert.NotNull(ingress.TlsSecretName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeshEntrypointGraphBuilder_BuildsGraph()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new MeshEntrypointGraphBuilder("test-mesh", MeshType.DockerCompose);
|
||||
|
||||
// Act
|
||||
var graph = builder
|
||||
.WithNamespace("my-project")
|
||||
.WithService(new ServiceNode
|
||||
{
|
||||
ServiceId = "web",
|
||||
ContainerName = "web",
|
||||
ImageDigest = "sha256:abc",
|
||||
Entrypoints = [],
|
||||
ExposedPorts = [80]
|
||||
})
|
||||
.WithService(new ServiceNode
|
||||
{
|
||||
ServiceId = "db",
|
||||
ContainerName = "db",
|
||||
ImageDigest = "sha256:def",
|
||||
Entrypoints = [],
|
||||
ExposedPorts = [5432]
|
||||
})
|
||||
.WithEdge(new CrossContainerEdge
|
||||
{
|
||||
EdgeId = "web->db",
|
||||
SourceServiceId = "web",
|
||||
TargetServiceId = "db",
|
||||
TargetPort = 5432
|
||||
})
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test-mesh", graph.MeshId);
|
||||
Assert.Equal(MeshType.DockerCompose, graph.Type);
|
||||
Assert.Equal(2, graph.Services.Length);
|
||||
Assert.Single(graph.Edges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeshType_AllValuesAreDistinct()
|
||||
{
|
||||
// Assert
|
||||
var values = Enum.GetValues<MeshType>();
|
||||
var distinctCount = values.Distinct().Count();
|
||||
Assert.Equal(values.Length, distinctCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeshEntrypointGraph_MultiplePaths_FindsAll()
|
||||
{
|
||||
// Arrange - Diamond pattern: A -> B -> D, A -> C -> D
|
||||
var services = new[]
|
||||
{
|
||||
CreateServiceNode("A"),
|
||||
CreateServiceNode("B"),
|
||||
CreateServiceNode("C"),
|
||||
CreateServiceNode("D")
|
||||
}.ToImmutableArray();
|
||||
|
||||
var edges = new[]
|
||||
{
|
||||
CreateEdge("A", "B"),
|
||||
CreateEdge("A", "C"),
|
||||
CreateEdge("B", "D"),
|
||||
CreateEdge("C", "D")
|
||||
}.ToImmutableArray();
|
||||
|
||||
var ingress = new[]
|
||||
{
|
||||
new IngressPath
|
||||
{
|
||||
IngressName = "main",
|
||||
Host = "test.com",
|
||||
Path = "/",
|
||||
TargetServiceId = "A",
|
||||
TargetPort = 80
|
||||
}
|
||||
}.ToImmutableArray();
|
||||
|
||||
var graph = new MeshEntrypointGraph
|
||||
{
|
||||
MeshId = "diamond",
|
||||
MeshId = "test-mesh",
|
||||
Services = ImmutableArray.Create(frontend, backend),
|
||||
Edges = ImmutableArray.Create(edge),
|
||||
IngressPaths = ImmutableArray.Create(ingress),
|
||||
Type = MeshType.Kubernetes,
|
||||
Services = services,
|
||||
Edges = edges,
|
||||
IngressPaths = ingress,
|
||||
AnalyzedAt = DateTime.UtcNow.ToString("O")
|
||||
AnalyzedAt = "2025-12-20T12:00:00Z"
|
||||
};
|
||||
|
||||
// Act
|
||||
var paths = graph.FindPathsToService("D", maxDepth: 5);
|
||||
var paths = graph.FindPathsToService("backend");
|
||||
|
||||
// Assert - Should find both paths: A->B->D and A->C->D
|
||||
Assert.Equal(2, paths.Length);
|
||||
Assert.All(paths, p => Assert.True(p.IsExternallyExposed));
|
||||
// Assert
|
||||
Assert.NotEmpty(paths);
|
||||
Assert.True(paths[0].IsIngressExposed);
|
||||
Assert.NotNull(paths[0].IngressPath);
|
||||
Assert.Equal("api.example.com", paths[0].IngressPath.Host);
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private static ImmutableArray<ServiceNode> CreateServiceNodes(int count)
|
||||
[Fact]
|
||||
public void FindPathsToService_NoIngress_ReturnsEmpty()
|
||||
{
|
||||
var builder = ImmutableArray.CreateBuilder<ServiceNode>(count);
|
||||
for (var i = 0; i < count; i++)
|
||||
// Arrange
|
||||
var frontend = CreateServiceNode("frontend");
|
||||
var backend = CreateServiceNode("backend");
|
||||
|
||||
var edge = new CrossContainerEdge
|
||||
{
|
||||
builder.Add(CreateServiceNode($"svc-{i}"));
|
||||
}
|
||||
return builder.ToImmutable();
|
||||
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<IngressPath>.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)
|
||||
@@ -374,23 +244,324 @@ public sealed class MeshEntrypointGraphTests
|
||||
{
|
||||
ServiceId = serviceId,
|
||||
ContainerName = serviceId,
|
||||
ImageDigest = $"sha256:{serviceId}",
|
||||
ImageReference = $"{serviceId}:latest",
|
||||
Entrypoints = [],
|
||||
ExposedPorts = [8080]
|
||||
ImageDigest = "sha256:" + serviceId,
|
||||
Entrypoints = ImmutableArray<SemanticEntrypoint>.Empty,
|
||||
ExposedPorts = ImmutableArray.Create(8080)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static CrossContainerEdge CreateEdge(string from, string to)
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="ServiceNode"/>.
|
||||
/// </summary>
|
||||
public sealed class ServiceNodeTests
|
||||
{
|
||||
[Fact]
|
||||
public void InternalDns_DefaultsToEmpty()
|
||||
{
|
||||
return new CrossContainerEdge
|
||||
// Arrange
|
||||
var node = new ServiceNode
|
||||
{
|
||||
EdgeId = $"{from}->{to}",
|
||||
SourceServiceId = from,
|
||||
TargetServiceId = to,
|
||||
ServiceId = "myapp",
|
||||
ContainerName = "myapp",
|
||||
ImageDigest = "sha256:abc",
|
||||
Entrypoints = ImmutableArray<SemanticEntrypoint>.Empty,
|
||||
ExposedPorts = ImmutableArray<int>.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<SemanticEntrypoint>.Empty,
|
||||
ExposedPorts = ImmutableArray<int>.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<SemanticEntrypoint>.Empty,
|
||||
ExposedPorts = ImmutableArray<int>.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<SemanticEntrypoint>.Empty,
|
||||
ExposedPorts = ImmutableArray<int>.Empty
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.False(node.IsSidecar);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="CrossContainerEdge"/>.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="CrossContainerPath"/>.
|
||||
/// </summary>
|
||||
public sealed class CrossContainerPathTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetAllVulnerableComponents_CombinesSourceAndTarget()
|
||||
{
|
||||
// Arrange
|
||||
var source = new ServiceNode
|
||||
{
|
||||
ServiceId = "frontend",
|
||||
ContainerName = "frontend",
|
||||
ImageDigest = "sha256:aaa",
|
||||
Entrypoints = ImmutableArray<SemanticEntrypoint>.Empty,
|
||||
ExposedPorts = ImmutableArray<int>.Empty,
|
||||
VulnerableComponents = ImmutableArray.Create("pkg:npm/lodash@4.17.20")
|
||||
};
|
||||
|
||||
var target = new ServiceNode
|
||||
{
|
||||
ServiceId = "backend",
|
||||
ContainerName = "backend",
|
||||
ImageDigest = "sha256:bbb",
|
||||
Entrypoints = ImmutableArray<SemanticEntrypoint>.Empty,
|
||||
ExposedPorts = ImmutableArray<int>.Empty,
|
||||
VulnerableComponents = ImmutableArray.Create("pkg:maven/log4j/log4j-core@2.14.1")
|
||||
};
|
||||
|
||||
var path = new CrossContainerPath
|
||||
{
|
||||
Source = source,
|
||||
Target = target,
|
||||
Hops = ImmutableArray<CrossContainerEdge>.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<SemanticEntrypoint>.Empty,
|
||||
ExposedPorts = ImmutableArray<int>.Empty,
|
||||
VulnerableComponents = ImmutableArray.Create(sharedVuln)
|
||||
};
|
||||
|
||||
var target = new ServiceNode
|
||||
{
|
||||
ServiceId = "backend",
|
||||
ContainerName = "backend",
|
||||
ImageDigest = "sha256:bbb",
|
||||
Entrypoints = ImmutableArray<SemanticEntrypoint>.Empty,
|
||||
ExposedPorts = ImmutableArray<int>.Empty,
|
||||
VulnerableComponents = ImmutableArray.Create(sharedVuln)
|
||||
};
|
||||
|
||||
var path = new CrossContainerPath
|
||||
{
|
||||
Source = source,
|
||||
Target = target,
|
||||
Hops = ImmutableArray<CrossContainerEdge>.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<SemanticEntrypoint>.Empty,
|
||||
ExposedPorts = ImmutableArray<int>.Empty
|
||||
};
|
||||
|
||||
var target = new ServiceNode
|
||||
{
|
||||
ServiceId = "backend",
|
||||
ContainerName = "backend",
|
||||
ImageDigest = "sha256:bbb",
|
||||
Entrypoints = ImmutableArray<SemanticEntrypoint>.Empty,
|
||||
ExposedPorts = ImmutableArray<int>.Empty
|
||||
};
|
||||
|
||||
var path = new CrossContainerPath
|
||||
{
|
||||
Source = source,
|
||||
Target = target,
|
||||
Hops = ImmutableArray<CrossContainerEdge>.Empty,
|
||||
HopCount = 0,
|
||||
IsIngressExposed = false,
|
||||
ReachabilityConfidence = 1.0f
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.Empty(path.VulnerableComponents);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="IngressPath"/>.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
#endregion
|
||||
[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<string, string>.Empty
|
||||
.Add("nginx.ingress.kubernetes.io/rewrite-target", "/")
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(ingress.Annotations);
|
||||
Assert.Contains("nginx.ingress.kubernetes.io/rewrite-target", ingress.Annotations.Keys);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user