Files
git.stella-ops.org/src/__Libraries/__Tests/StellaOps.Microservice.Tests/RouterConnectionManagerTests.cs

350 lines
9.5 KiB
C#

using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using StellaOps.Router.Common.Abstractions;
using StellaOps.Router.Common.Enums;
using StellaOps.Router.Common.Models;
using StellaOps.TestKit;
namespace StellaOps.Microservice.Tests;
/// <summary>
/// Unit tests for <see cref="RouterConnectionManager"/>.
/// </summary>
public sealed class RouterConnectionManagerTests : IDisposable
{
private readonly Mock<IEndpointDiscoveryProvider> _discoveryProviderMock;
private readonly Mock<IMicroserviceTransport> _transportMock;
private readonly StellaMicroserviceOptions _options;
public RouterConnectionManagerTests()
{
_discoveryProviderMock = new Mock<IEndpointDiscoveryProvider>();
_transportMock = new Mock<IMicroserviceTransport>();
_options = new StellaMicroserviceOptions
{
ServiceName = "test-service",
Version = "1.0.0",
Region = "test",
InstanceId = "test-instance-1",
HeartbeatInterval = TimeSpan.FromMilliseconds(50),
ReconnectBackoffInitial = TimeSpan.FromMilliseconds(10),
ReconnectBackoffMax = TimeSpan.FromMilliseconds(100)
};
_discoveryProviderMock.Setup(d => d.DiscoverEndpoints())
.Returns(new List<EndpointDescriptor>());
}
public void Dispose()
{
// Cleanup if needed
}
private RouterConnectionManager CreateManager()
{
return new RouterConnectionManager(
Options.Create(_options),
_discoveryProviderMock.Object,
_transportMock.Object,
NullLogger<RouterConnectionManager>.Instance);
}
#region Constructor Tests
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Constructor_InitializesCorrectly()
{
// Act
using var manager = CreateManager();
// Assert
manager.Connections.Should().BeEmpty();
manager.CurrentStatus.Should().Be(InstanceHealthStatus.Healthy);
manager.InFlightRequestCount.Should().Be(0);
manager.ErrorRate.Should().Be(0);
}
#endregion
#region CurrentStatus Tests
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CurrentStatus_CanBeSet()
{
// Arrange
using var manager = CreateManager();
// Act
manager.CurrentStatus = InstanceHealthStatus.Draining;
// Assert
manager.CurrentStatus.Should().Be(InstanceHealthStatus.Draining);
}
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(InstanceHealthStatus.Healthy)]
[InlineData(InstanceHealthStatus.Degraded)]
[InlineData(InstanceHealthStatus.Draining)]
[InlineData(InstanceHealthStatus.Unhealthy)]
public void CurrentStatus_AcceptsAllStatusValues(InstanceHealthStatus status)
{
// Arrange
using var manager = CreateManager();
// Act
manager.CurrentStatus = status;
// Assert
manager.CurrentStatus.Should().Be(status);
}
#endregion
#region InFlightRequestCount Tests
[Trait("Category", TestCategories.Unit)]
[Fact]
public void InFlightRequestCount_CanBeSet()
{
// Arrange
using var manager = CreateManager();
// Act
manager.InFlightRequestCount = 42;
// Assert
manager.InFlightRequestCount.Should().Be(42);
}
#endregion
#region ErrorRate Tests
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ErrorRate_CanBeSet()
{
// Arrange
using var manager = CreateManager();
// Act
manager.ErrorRate = 0.25;
// Assert
manager.ErrorRate.Should().Be(0.25);
}
#endregion
#region StartAsync Tests
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_DiscoversEndpoints()
{
// Arrange
_options.Routers.Add(new RouterEndpointConfig
{
Host = "localhost",
Port = 5000,
TransportType = TransportType.InMemory
});
using var manager = CreateManager();
// Act
await manager.StartAsync(CancellationToken.None);
await Task.Delay(10);
await manager.StopAsync(CancellationToken.None);
// Assert
_discoveryProviderMock.Verify(d => d.DiscoverEndpoints(), Times.Once);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_WithRouters_CreatesConnections()
{
// Arrange
_options.Routers.Add(new RouterEndpointConfig
{
Host = "localhost",
Port = 5000,
TransportType = TransportType.InMemory
});
using var manager = CreateManager();
// Act
await manager.StartAsync(CancellationToken.None);
// Assert
manager.Connections.Should().HaveCount(1);
manager.Connections[0].Instance.ServiceName.Should().Be("test-service");
// Cleanup
await manager.StopAsync(CancellationToken.None);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_RegistersEndpointsInConnection()
{
// Arrange
_options.Routers.Add(new RouterEndpointConfig
{
Host = "localhost",
Port = 5000,
TransportType = TransportType.InMemory
});
var endpoints = new List<EndpointDescriptor>
{
new() { ServiceName = "test", Version = "1.0", Method = "GET", Path = "/api/users" },
new() { ServiceName = "test", Version = "1.0", Method = "POST", Path = "/api/users" }
};
_discoveryProviderMock.Setup(d => d.DiscoverEndpoints()).Returns(endpoints);
using var manager = CreateManager();
// Act
await manager.StartAsync(CancellationToken.None);
// Assert
manager.Connections[0].Endpoints.Should().HaveCount(2);
// Cleanup
await manager.StopAsync(CancellationToken.None);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_AfterDispose_ThrowsObjectDisposedException()
{
// Arrange
var manager = CreateManager();
manager.Dispose();
// Act
var action = () => manager.StartAsync(CancellationToken.None);
// Assert
await action.Should().ThrowAsync<ObjectDisposedException>();
}
#endregion
#region StopAsync Tests
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StopAsync_ClearsConnections()
{
// Arrange
_options.Routers.Add(new RouterEndpointConfig
{
Host = "localhost",
Port = 5000,
TransportType = TransportType.InMemory
});
using var manager = CreateManager();
await manager.StartAsync(CancellationToken.None);
// Act
await manager.StopAsync(CancellationToken.None);
// Assert
manager.Connections.Should().BeEmpty();
}
#endregion
#region Heartbeat Tests
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Heartbeat_SendsViaTransport()
{
// Arrange
_options.Routers.Add(new RouterEndpointConfig
{
Host = "localhost",
Port = 5000,
TransportType = TransportType.InMemory
});
using var manager = CreateManager();
// Act
await manager.StartAsync(CancellationToken.None);
await Task.Delay(150); // Wait for heartbeat to run
await manager.StopAsync(CancellationToken.None);
// Assert
_transportMock.Verify(
t => t.SendHeartbeatAsync(It.IsAny<HeartbeatPayload>(), It.IsAny<CancellationToken>()),
Times.AtLeastOnce);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Heartbeat_IncludesCurrentMetrics()
{
// Arrange
_options.Routers.Add(new RouterEndpointConfig
{
Host = "localhost",
Port = 5000,
TransportType = TransportType.InMemory
});
using var manager = CreateManager();
using StellaOps.TestKit;
manager.CurrentStatus = InstanceHealthStatus.Degraded;
manager.InFlightRequestCount = 10;
manager.ErrorRate = 0.05;
HeartbeatPayload? capturedHeartbeat = null;
_transportMock.Setup(t => t.SendHeartbeatAsync(It.IsAny<HeartbeatPayload>(), It.IsAny<CancellationToken>()))
.Callback<HeartbeatPayload, CancellationToken>((h, _) => capturedHeartbeat = h)
.Returns(Task.CompletedTask);
// Act
await manager.StartAsync(CancellationToken.None);
await Task.Delay(150); // Wait for heartbeat
await manager.StopAsync(CancellationToken.None);
// Assert
capturedHeartbeat.Should().NotBeNull();
capturedHeartbeat!.Status.Should().Be(InstanceHealthStatus.Degraded);
capturedHeartbeat.InFlightRequestCount.Should().Be(10);
capturedHeartbeat.ErrorRate.Should().Be(0.05);
}
#endregion
#region Dispose Tests
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Dispose_CanBeCalledMultipleTimes()
{
// Arrange
var manager = CreateManager();
// Act
var action = () =>
{
manager.Dispose();
manager.Dispose();
manager.Dispose();
};
// Assert
action.Should().NotThrow();
}
#endregion
}