- Updated JavaScriptCallGraphExtractorTests to improve naming conventions and test cases for Azure Functions, CLI commands, and socket handling. - Modified NodeCallGraphExtractorTests to correctly assert exceptions for null inputs. - Enhanced WitnessModalComponent tests in Angular to use Jasmine spies and improved assertions for path visualization and signature verification. - Added ConnectionState property for tracking connection establishment time in Router.Common. - Implemented validation for HelloPayload in ConnectionManager to ensure required fields are present. - Introduced RabbitMqContainerFixture method for restarting RabbitMQ container during tests. - Added integration tests for RabbitMq to verify connection recovery after broker restarts. - Created new BinaryCallGraphExtractorTests, GoCallGraphExtractorTests, and PythonCallGraphExtractorTests for comprehensive coverage of binary, Go, and Python call graph extraction functionalities. - Developed ConnectionManagerTests to validate connection handling, including rejection of invalid hello messages and proper cleanup on client disconnects.
227 lines
7.0 KiB
C#
227 lines
7.0 KiB
C#
using FluentAssertions;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Options;
|
|
using StellaOps.Router.Common.Enums;
|
|
using StellaOps.Router.Common.Models;
|
|
using StellaOps.Router.Gateway.Services;
|
|
using StellaOps.Router.Gateway.State;
|
|
using StellaOps.Router.Transport.InMemory;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Router.Gateway.Tests;
|
|
|
|
public sealed class ConnectionManagerTests
|
|
{
|
|
[Fact]
|
|
public async Task StartAsync_WhenHelloInvalid_RejectsAndClosesChannel()
|
|
{
|
|
var (manager, server, registry, routingState) = Create();
|
|
var client = CreateClient(registry, server);
|
|
|
|
try
|
|
{
|
|
await manager.StartAsync(CancellationToken.None);
|
|
|
|
var invalid = new InstanceDescriptor
|
|
{
|
|
InstanceId = "inv-1",
|
|
ServiceName = "",
|
|
Version = "1.0.0",
|
|
Region = "eu1"
|
|
};
|
|
|
|
await client.ConnectAsync(
|
|
invalid,
|
|
endpoints:
|
|
[
|
|
new EndpointDescriptor
|
|
{
|
|
ServiceName = "inventory",
|
|
Version = "1.0.0",
|
|
Method = "GET",
|
|
Path = "/items"
|
|
}
|
|
],
|
|
CancellationToken.None);
|
|
|
|
await EventuallyAsync(
|
|
() => registry.Count == 0,
|
|
timeout: TimeSpan.FromSeconds(5));
|
|
|
|
routingState.GetAllConnections().Should().BeEmpty();
|
|
}
|
|
finally
|
|
{
|
|
client.Dispose();
|
|
await manager.StopAsync(CancellationToken.None);
|
|
server.Dispose();
|
|
registry.Dispose();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WhenClientDisconnects_RemovesFromRoutingState()
|
|
{
|
|
var (manager, server, registry, routingState) = Create();
|
|
var client = CreateClient(registry, server);
|
|
|
|
try
|
|
{
|
|
await manager.StartAsync(CancellationToken.None);
|
|
|
|
await client.ConnectAsync(
|
|
CreateInstance("inventory", "1.0.0", "inv-1"),
|
|
endpoints:
|
|
[
|
|
new EndpointDescriptor
|
|
{
|
|
ServiceName = "inventory",
|
|
Version = "1.0.0",
|
|
Method = "GET",
|
|
Path = "/items"
|
|
}
|
|
],
|
|
CancellationToken.None);
|
|
|
|
await EventuallyAsync(
|
|
() => routingState.GetAllConnections().Count == 1,
|
|
timeout: TimeSpan.FromSeconds(5));
|
|
|
|
client.Dispose();
|
|
|
|
await EventuallyAsync(
|
|
() => routingState.GetAllConnections().Count == 0,
|
|
timeout: TimeSpan.FromSeconds(5));
|
|
}
|
|
finally
|
|
{
|
|
client.Dispose();
|
|
await manager.StopAsync(CancellationToken.None);
|
|
server.Dispose();
|
|
registry.Dispose();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WhenMultipleClientsConnect_TracksAndCleansIndependently()
|
|
{
|
|
var (manager, server, registry, routingState) = Create();
|
|
var client1 = CreateClient(registry, server);
|
|
var client2 = CreateClient(registry, server);
|
|
|
|
try
|
|
{
|
|
await manager.StartAsync(CancellationToken.None);
|
|
|
|
var endpoints =
|
|
new[]
|
|
{
|
|
new EndpointDescriptor
|
|
{
|
|
ServiceName = "inventory",
|
|
Version = "1.0.0",
|
|
Method = "GET",
|
|
Path = "/items"
|
|
}
|
|
};
|
|
|
|
await client1.ConnectAsync(
|
|
CreateInstance("inventory", "1.0.0", "inv-1"),
|
|
endpoints,
|
|
CancellationToken.None);
|
|
|
|
await client2.ConnectAsync(
|
|
CreateInstance("inventory", "1.0.0", "inv-2"),
|
|
endpoints,
|
|
CancellationToken.None);
|
|
|
|
await EventuallyAsync(
|
|
() => routingState.GetConnectionsFor("inventory", "1.0.0", "GET", "/items").Count == 2,
|
|
timeout: TimeSpan.FromSeconds(5));
|
|
|
|
var before = routingState.GetConnectionsFor("inventory", "1.0.0", "GET", "/items")
|
|
.Select(c => c.Instance.InstanceId)
|
|
.ToHashSet(StringComparer.Ordinal);
|
|
|
|
before.Should().BeEquivalentTo(new[] { "inv-1", "inv-2" });
|
|
|
|
client1.Dispose();
|
|
|
|
await EventuallyAsync(
|
|
() => routingState.GetConnectionsFor("inventory", "1.0.0", "GET", "/items").Count == 1,
|
|
timeout: TimeSpan.FromSeconds(5));
|
|
|
|
var after = routingState.GetConnectionsFor("inventory", "1.0.0", "GET", "/items")
|
|
.Single()
|
|
.Instance.InstanceId;
|
|
|
|
after.Should().Be("inv-2");
|
|
}
|
|
finally
|
|
{
|
|
client1.Dispose();
|
|
client2.Dispose();
|
|
await manager.StopAsync(CancellationToken.None);
|
|
server.Dispose();
|
|
registry.Dispose();
|
|
}
|
|
}
|
|
|
|
private static (ConnectionManager Manager, InMemoryTransportServer Server, InMemoryConnectionRegistry Registry, InMemoryRoutingState RoutingState) Create()
|
|
{
|
|
var registry = new InMemoryConnectionRegistry();
|
|
var server = new InMemoryTransportServer(
|
|
registry,
|
|
Options.Create(new InMemoryTransportOptions()),
|
|
NullLogger<InMemoryTransportServer>.Instance);
|
|
|
|
var routingState = new InMemoryRoutingState();
|
|
var manager = new ConnectionManager(
|
|
server,
|
|
registry,
|
|
routingState,
|
|
NullLogger<ConnectionManager>.Instance);
|
|
|
|
return (manager, server, registry, routingState);
|
|
}
|
|
|
|
private static InMemoryTransportClient CreateClient(InMemoryConnectionRegistry registry, InMemoryTransportServer server)
|
|
{
|
|
return new InMemoryTransportClient(
|
|
registry,
|
|
Options.Create(new InMemoryTransportOptions()),
|
|
NullLogger<InMemoryTransportClient>.Instance,
|
|
server);
|
|
}
|
|
|
|
private static InstanceDescriptor CreateInstance(string serviceName, string version, string instanceId)
|
|
{
|
|
return new InstanceDescriptor
|
|
{
|
|
InstanceId = instanceId,
|
|
ServiceName = serviceName,
|
|
Version = version,
|
|
Region = "eu1"
|
|
};
|
|
}
|
|
|
|
private static async Task EventuallyAsync(Func<bool> predicate, TimeSpan timeout, TimeSpan? pollInterval = null)
|
|
{
|
|
pollInterval ??= TimeSpan.FromMilliseconds(25);
|
|
var deadline = DateTime.UtcNow + timeout;
|
|
|
|
while (DateTime.UtcNow < deadline)
|
|
{
|
|
if (predicate())
|
|
{
|
|
return;
|
|
}
|
|
|
|
await Task.Delay(pollInterval.Value);
|
|
}
|
|
|
|
predicate().Should().BeTrue("condition should become true within {0}", timeout);
|
|
}
|
|
}
|
|
|