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; /// /// Unit tests for . /// public sealed class RouterConnectionManagerTests : IDisposable { private readonly Mock _discoveryProviderMock; private readonly Mock _transportMock; private readonly StellaMicroserviceOptions _options; public RouterConnectionManagerTests() { _discoveryProviderMock = new Mock(); _transportMock = new Mock(); _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()); } public void Dispose() { // Cleanup if needed } private RouterConnectionManager CreateManager() { return new RouterConnectionManager( Options.Create(_options), _discoveryProviderMock.Object, _transportMock.Object, NullLogger.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 { 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(); } #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(), It.IsAny()), 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(), It.IsAny())) .Callback((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 }