Add unit tests for RabbitMq and Udp transport servers and clients
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Implemented comprehensive unit tests for RabbitMqTransportServer, covering constructor, disposal, connection management, event handlers, and exception handling. - Added configuration tests for RabbitMqTransportServer to validate SSL, durable queues, auto-recovery, and custom virtual host options. - Created unit tests for UdpFrameProtocol, including frame parsing and serialization, header size validation, and round-trip data preservation. - Developed tests for UdpTransportClient, focusing on connection handling, event subscriptions, and exception scenarios. - Established tests for UdpTransportServer, ensuring proper start/stop behavior, connection state management, and event handling. - Included tests for UdpTransportOptions to verify default values and modification capabilities. - Enhanced service registration tests for Udp transport services in the dependency injection container.
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for router connection manager.
|
||||
/// </summary>
|
||||
[Collection("Microservice Integration")]
|
||||
public sealed class ConnectionManagerIntegrationTests
|
||||
{
|
||||
private readonly MicroserviceIntegrationFixture _fixture;
|
||||
|
||||
public ConnectionManagerIntegrationTests(MicroserviceIntegrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
#region Initialization Tests
|
||||
|
||||
[Fact]
|
||||
public void ConnectionManager_IsInitialized()
|
||||
{
|
||||
// Arrange & Act
|
||||
var connectionManager = _fixture.ConnectionManager;
|
||||
|
||||
// Assert
|
||||
connectionManager.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectionManager_HasConnections()
|
||||
{
|
||||
// Arrange
|
||||
var connectionManager = _fixture.ConnectionManager;
|
||||
|
||||
// Act
|
||||
var connections = connectionManager.Connections;
|
||||
|
||||
// Assert
|
||||
connections.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectionManager_ConnectionHasCorrectServiceInfo()
|
||||
{
|
||||
// Arrange
|
||||
var connectionManager = _fixture.ConnectionManager;
|
||||
|
||||
// Act
|
||||
var connection = connectionManager.Connections.FirstOrDefault();
|
||||
|
||||
// Assert
|
||||
connection.Should().NotBeNull();
|
||||
connection!.Instance.ServiceName.Should().Be("test-service");
|
||||
connection.Instance.Version.Should().Be("1.0.0");
|
||||
connection.Instance.Region.Should().Be("test-region");
|
||||
connection.Instance.InstanceId.Should().Be("test-instance-001");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectionManager_ConnectionHasEndpoints()
|
||||
{
|
||||
// Arrange
|
||||
var connectionManager = _fixture.ConnectionManager;
|
||||
|
||||
// Act
|
||||
var connection = connectionManager.Connections.FirstOrDefault();
|
||||
|
||||
// Assert
|
||||
connection!.Endpoints.Should().HaveCount(8);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Status Tests
|
||||
|
||||
[Fact]
|
||||
public void ConnectionManager_DefaultStatus_IsHealthy()
|
||||
{
|
||||
// Arrange
|
||||
var connectionManager = _fixture.ConcreteConnectionManager;
|
||||
|
||||
// Act
|
||||
var status = connectionManager.CurrentStatus;
|
||||
|
||||
// Assert
|
||||
status.Should().Be(InstanceHealthStatus.Healthy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectionManager_CanChangeStatus()
|
||||
{
|
||||
// Arrange
|
||||
var connectionManager = _fixture.ConcreteConnectionManager;
|
||||
var originalStatus = connectionManager.CurrentStatus;
|
||||
|
||||
// Act
|
||||
connectionManager.CurrentStatus = InstanceHealthStatus.Degraded;
|
||||
var newStatus = connectionManager.CurrentStatus;
|
||||
|
||||
// Cleanup
|
||||
connectionManager.CurrentStatus = originalStatus;
|
||||
|
||||
// Assert
|
||||
newStatus.Should().Be(InstanceHealthStatus.Degraded);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(InstanceHealthStatus.Healthy)]
|
||||
[InlineData(InstanceHealthStatus.Degraded)]
|
||||
[InlineData(InstanceHealthStatus.Draining)]
|
||||
[InlineData(InstanceHealthStatus.Unhealthy)]
|
||||
public void ConnectionManager_AcceptsAllStatusValues(InstanceHealthStatus status)
|
||||
{
|
||||
// Arrange
|
||||
var connectionManager = _fixture.ConcreteConnectionManager;
|
||||
var originalStatus = connectionManager.CurrentStatus;
|
||||
|
||||
// Act
|
||||
connectionManager.CurrentStatus = status;
|
||||
var actualStatus = connectionManager.CurrentStatus;
|
||||
|
||||
// Cleanup
|
||||
connectionManager.CurrentStatus = originalStatus;
|
||||
|
||||
// Assert
|
||||
actualStatus.Should().Be(status);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Metrics Tests
|
||||
|
||||
[Fact]
|
||||
public void ConnectionManager_InFlightRequestCount_InitiallyZero()
|
||||
{
|
||||
// Arrange
|
||||
var connectionManager = _fixture.ConcreteConnectionManager;
|
||||
|
||||
// Act
|
||||
var count = connectionManager.InFlightRequestCount;
|
||||
|
||||
// Assert
|
||||
count.Should().BeGreaterOrEqualTo(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectionManager_ErrorRate_InitiallyZero()
|
||||
{
|
||||
// Arrange
|
||||
var connectionManager = _fixture.ConcreteConnectionManager;
|
||||
|
||||
// Act
|
||||
var errorRate = connectionManager.ErrorRate;
|
||||
|
||||
// Assert
|
||||
errorRate.Should().BeGreaterOrEqualTo(0);
|
||||
errorRate.Should().BeLessThanOrEqualTo(1.0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectionManager_CanSetInFlightRequestCount()
|
||||
{
|
||||
// Arrange
|
||||
var connectionManager = _fixture.ConcreteConnectionManager;
|
||||
var originalCount = connectionManager.InFlightRequestCount;
|
||||
|
||||
// Act
|
||||
connectionManager.InFlightRequestCount = 42;
|
||||
var newCount = connectionManager.InFlightRequestCount;
|
||||
|
||||
// Cleanup
|
||||
connectionManager.InFlightRequestCount = originalCount;
|
||||
|
||||
// Assert
|
||||
newCount.Should().Be(42);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectionManager_CanSetErrorRate()
|
||||
{
|
||||
// Arrange
|
||||
var connectionManager = _fixture.ConcreteConnectionManager;
|
||||
var originalRate = connectionManager.ErrorRate;
|
||||
|
||||
// Act
|
||||
connectionManager.ErrorRate = 0.15;
|
||||
var newRate = connectionManager.ErrorRate;
|
||||
|
||||
// Cleanup
|
||||
connectionManager.ErrorRate = originalRate;
|
||||
|
||||
// Assert
|
||||
newRate.Should().Be(0.15);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using StellaOps.Microservice;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for endpoint registry and discovery.
|
||||
/// </summary>
|
||||
[Collection("Microservice Integration")]
|
||||
public sealed class EndpointRegistryIntegrationTests
|
||||
{
|
||||
private readonly MicroserviceIntegrationFixture _fixture;
|
||||
|
||||
public EndpointRegistryIntegrationTests(MicroserviceIntegrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
#region Endpoint Discovery Tests
|
||||
|
||||
[Fact]
|
||||
public void Registry_ContainsAllTestEndpoints()
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
var endpoints = registry.GetAllEndpoints();
|
||||
|
||||
// Assert
|
||||
endpoints.Should().HaveCount(8);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("POST", "/echo")]
|
||||
[InlineData("GET", "/users/123")]
|
||||
[InlineData("POST", "/users")]
|
||||
[InlineData("POST", "/slow")]
|
||||
[InlineData("POST", "/fail")]
|
||||
[InlineData("POST", "/stream")]
|
||||
[InlineData("DELETE", "/admin/reset")]
|
||||
[InlineData("GET", "/quick")]
|
||||
public void Registry_FindsEndpoint_ByMethodAndPath(string method, string path)
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
var found = registry.TryMatch(method, path, out var match);
|
||||
|
||||
// Assert
|
||||
found.Should().BeTrue();
|
||||
match.Should().NotBeNull();
|
||||
match!.Endpoint.Method.Should().Be(method);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Registry_ReturnsNull_ForUnknownEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
var found = registry.TryMatch("GET", "/unknown", out var match);
|
||||
|
||||
// Assert
|
||||
found.Should().BeFalse();
|
||||
match.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Registry_MatchesPathParameters()
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
var found = registry.TryMatch("GET", "/users/12345", out var match);
|
||||
|
||||
// Assert
|
||||
found.Should().BeTrue();
|
||||
match!.Endpoint.Path.Should().Be("/users/{userId}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Registry_ExtractsPathParameters()
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
registry.TryMatch("GET", "/users/abc123", out var match);
|
||||
|
||||
// Assert
|
||||
match.Should().NotBeNull();
|
||||
match!.PathParameters.Should().ContainKey("userId");
|
||||
match.PathParameters["userId"].Should().Be("abc123");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Endpoint Metadata Tests
|
||||
|
||||
[Fact]
|
||||
public void Endpoint_HasCorrectTimeout()
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
registry.TryMatch("GET", "/quick", out var quickMatch);
|
||||
registry.TryMatch("POST", "/slow", out var slowMatch);
|
||||
|
||||
// Assert
|
||||
quickMatch!.Endpoint.DefaultTimeout.Should().Be(TimeSpan.FromSeconds(5));
|
||||
slowMatch!.Endpoint.DefaultTimeout.Should().Be(TimeSpan.FromSeconds(60));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoint_HasCorrectStreamingFlag()
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
registry.TryMatch("POST", "/stream", out var streamMatch);
|
||||
registry.TryMatch("POST", "/echo", out var echoMatch);
|
||||
|
||||
// Assert
|
||||
streamMatch!.Endpoint.SupportsStreaming.Should().BeTrue();
|
||||
echoMatch!.Endpoint.SupportsStreaming.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoint_HasCorrectClaims()
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
registry.TryMatch("DELETE", "/admin/reset", out var adminMatch);
|
||||
registry.TryMatch("POST", "/echo", out var echoMatch);
|
||||
|
||||
// Assert
|
||||
adminMatch!.Endpoint.RequiringClaims.Should().HaveCount(2);
|
||||
adminMatch.Endpoint.RequiringClaims.Should().Contain(c => c.Type == "admin");
|
||||
adminMatch.Endpoint.RequiringClaims.Should().Contain(c => c.Type == "write");
|
||||
echoMatch!.Endpoint.RequiringClaims.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Endpoint_HasCorrectHandlerType()
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
registry.TryMatch("POST", "/echo", out var match);
|
||||
|
||||
// Assert
|
||||
match!.Endpoint.HandlerType.Should().Be(typeof(EchoEndpoint));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using StellaOps.Microservice;
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Transport.InMemory;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Router.Integration.Tests.Fixtures;
|
||||
|
||||
/// <summary>
|
||||
/// Test fixture that sets up a microservice with InMemory transport for integration testing.
|
||||
/// </summary>
|
||||
public sealed class MicroserviceIntegrationFixture : IAsyncLifetime
|
||||
{
|
||||
private IHost? _host;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the service provider for the test microservice.
|
||||
/// </summary>
|
||||
public IServiceProvider Services => _host?.Services ?? throw new InvalidOperationException("Fixture not initialized");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the endpoint registry.
|
||||
/// </summary>
|
||||
public IEndpointRegistry EndpointRegistry => Services.GetRequiredService<IEndpointRegistry>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the router connection manager interface.
|
||||
/// </summary>
|
||||
public IRouterConnectionManager ConnectionManager => Services.GetRequiredService<IRouterConnectionManager>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the concrete router connection manager for accessing additional properties.
|
||||
/// </summary>
|
||||
public RouterConnectionManager ConcreteConnectionManager => (RouterConnectionManager)ConnectionManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the InMemory transport client for direct testing.
|
||||
/// </summary>
|
||||
public InMemoryTransportClient TransportClient => Services.GetRequiredService<InMemoryTransportClient>();
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
var builder = Host.CreateApplicationBuilder();
|
||||
|
||||
// Add InMemory transport
|
||||
builder.Services.AddInMemoryTransport();
|
||||
|
||||
// Add microservice with test discovery provider
|
||||
builder.Services.AddStellaMicroservice<TestEndpointDiscoveryProvider>(options =>
|
||||
{
|
||||
options.ServiceName = "test-service";
|
||||
options.Version = "1.0.0";
|
||||
options.Region = "test-region";
|
||||
options.InstanceId = "test-instance-001";
|
||||
options.HeartbeatInterval = TimeSpan.FromMilliseconds(100);
|
||||
options.ReconnectBackoffInitial = TimeSpan.FromMilliseconds(10);
|
||||
options.ReconnectBackoffMax = TimeSpan.FromMilliseconds(100);
|
||||
options.Routers.Add(new RouterEndpointConfig
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 5100,
|
||||
TransportType = TransportType.InMemory
|
||||
});
|
||||
});
|
||||
|
||||
// Register test endpoint handlers
|
||||
builder.Services.AddScoped<EchoEndpoint>();
|
||||
builder.Services.AddScoped<GetUserEndpoint>();
|
||||
builder.Services.AddScoped<CreateUserEndpoint>();
|
||||
builder.Services.AddScoped<SlowEndpoint>();
|
||||
builder.Services.AddScoped<FailEndpoint>();
|
||||
builder.Services.AddScoped<StreamEndpoint>();
|
||||
builder.Services.AddScoped<AdminResetEndpoint>();
|
||||
builder.Services.AddScoped<QuickEndpoint>();
|
||||
|
||||
_host = builder.Build();
|
||||
await _host.StartAsync();
|
||||
|
||||
// Wait for microservice to initialize
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
if (_host is not null)
|
||||
{
|
||||
await _host.StopAsync();
|
||||
_host.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection definition for sharing fixture across test classes.
|
||||
/// </summary>
|
||||
[CollectionDefinition("Microservice Integration")]
|
||||
public class MicroserviceIntegrationCollection : ICollectionFixture<MicroserviceIntegrationFixture>
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
using System.Text;
|
||||
using StellaOps.Microservice;
|
||||
|
||||
namespace StellaOps.Router.Integration.Tests.Fixtures;
|
||||
|
||||
#region Request/Response Types
|
||||
|
||||
public record EchoRequest(string Message);
|
||||
public record EchoResponse(string Echo, DateTime Timestamp);
|
||||
|
||||
public record GetUserRequest(string UserId);
|
||||
public record GetUserResponse(string UserId, string Name, string Email);
|
||||
|
||||
public record CreateUserRequest(string Name, string Email);
|
||||
public record CreateUserResponse(string UserId, bool Success);
|
||||
|
||||
public record SlowRequest(int DelayMs);
|
||||
public record SlowResponse(int ActualDelayMs);
|
||||
|
||||
public record FailRequest(string ErrorMessage);
|
||||
public record FailResponse();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Endpoints
|
||||
|
||||
/// <summary>
|
||||
/// Simple echo endpoint for basic request/response testing.
|
||||
/// </summary>
|
||||
[StellaEndpoint("POST", "/echo")]
|
||||
public sealed class EchoEndpoint : IStellaEndpoint<EchoRequest, EchoResponse>
|
||||
{
|
||||
public Task<EchoResponse> HandleAsync(EchoRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new EchoResponse($"Echo: {request.Message}", DateTime.UtcNow));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint with path parameters.
|
||||
/// </summary>
|
||||
[StellaEndpoint("GET", "/users/{userId}")]
|
||||
public sealed class GetUserEndpoint : IStellaEndpoint<GetUserRequest, GetUserResponse>
|
||||
{
|
||||
public Task<GetUserResponse> HandleAsync(GetUserRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new GetUserResponse(
|
||||
request.UserId,
|
||||
$"User-{request.UserId}",
|
||||
$"user-{request.UserId}@example.com"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// POST endpoint for creating resources.
|
||||
/// </summary>
|
||||
[StellaEndpoint("POST", "/users")]
|
||||
public sealed class CreateUserEndpoint : IStellaEndpoint<CreateUserRequest, CreateUserResponse>
|
||||
{
|
||||
public Task<CreateUserResponse> HandleAsync(CreateUserRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var userId = Guid.NewGuid().ToString("N")[..8];
|
||||
return Task.FromResult(new CreateUserResponse(userId, true));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint that deliberately delays to test timeouts and cancellation.
|
||||
/// </summary>
|
||||
[StellaEndpoint("POST", "/slow", TimeoutSeconds = 60)]
|
||||
public sealed class SlowEndpoint : IStellaEndpoint<SlowRequest, SlowResponse>
|
||||
{
|
||||
public async Task<SlowResponse> HandleAsync(SlowRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
await Task.Delay(request.DelayMs, cancellationToken);
|
||||
sw.Stop();
|
||||
return new SlowResponse((int)sw.ElapsedMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint that throws exceptions for error handling tests.
|
||||
/// </summary>
|
||||
[StellaEndpoint("POST", "/fail")]
|
||||
public sealed class FailEndpoint : IStellaEndpoint<FailRequest, FailResponse>
|
||||
{
|
||||
public Task<FailResponse> HandleAsync(FailRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new InvalidOperationException(request.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raw endpoint for streaming tests.
|
||||
/// </summary>
|
||||
[StellaEndpoint("POST", "/stream", SupportsStreaming = true)]
|
||||
public sealed class StreamEndpoint : IRawStellaEndpoint
|
||||
{
|
||||
public async Task<RawResponse> HandleAsync(RawRequestContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
// Read all input
|
||||
using var reader = new StreamReader(context.Body);
|
||||
var input = await reader.ReadToEndAsync(cancellationToken);
|
||||
|
||||
// Echo it back with prefix
|
||||
var output = $"Streamed: {input}";
|
||||
var outputBytes = Encoding.UTF8.GetBytes(output);
|
||||
|
||||
var response = new RawResponse
|
||||
{
|
||||
StatusCode = 200,
|
||||
Headers = new HeaderCollection([new KeyValuePair<string, string>("Content-Type", "text/plain")]),
|
||||
Body = new MemoryStream(outputBytes)
|
||||
};
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint requiring specific claims.
|
||||
/// </summary>
|
||||
[StellaEndpoint("DELETE", "/admin/reset", RequiredClaims = ["admin", "write"])]
|
||||
public sealed class AdminResetEndpoint : IStellaEndpoint<EchoRequest, EchoResponse>
|
||||
{
|
||||
public Task<EchoResponse> HandleAsync(EchoRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new EchoResponse("Admin action completed", DateTime.UtcNow));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint with custom timeout.
|
||||
/// </summary>
|
||||
[StellaEndpoint("GET", "/quick", TimeoutSeconds = 5)]
|
||||
public sealed class QuickEndpoint : IStellaEndpoint<EchoRequest, EchoResponse>
|
||||
{
|
||||
public Task<EchoResponse> HandleAsync(EchoRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new EchoResponse("Quick response", DateTime.UtcNow));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Endpoint Discovery Provider
|
||||
|
||||
/// <summary>
|
||||
/// Test endpoint discovery provider that returns our test endpoints.
|
||||
/// </summary>
|
||||
public sealed class TestEndpointDiscoveryProvider : IEndpointDiscoveryProvider
|
||||
{
|
||||
public IReadOnlyList<Router.Common.Models.EndpointDescriptor> DiscoverEndpoints()
|
||||
{
|
||||
return
|
||||
[
|
||||
new Router.Common.Models.EndpointDescriptor
|
||||
{
|
||||
ServiceName = "test-service",
|
||||
Version = "1.0.0",
|
||||
Method = "POST",
|
||||
Path = "/echo",
|
||||
DefaultTimeout = TimeSpan.FromSeconds(30),
|
||||
HandlerType = typeof(EchoEndpoint)
|
||||
},
|
||||
new Router.Common.Models.EndpointDescriptor
|
||||
{
|
||||
ServiceName = "test-service",
|
||||
Version = "1.0.0",
|
||||
Method = "GET",
|
||||
Path = "/users/{userId}",
|
||||
DefaultTimeout = TimeSpan.FromSeconds(30),
|
||||
HandlerType = typeof(GetUserEndpoint)
|
||||
},
|
||||
new Router.Common.Models.EndpointDescriptor
|
||||
{
|
||||
ServiceName = "test-service",
|
||||
Version = "1.0.0",
|
||||
Method = "POST",
|
||||
Path = "/users",
|
||||
DefaultTimeout = TimeSpan.FromSeconds(30),
|
||||
HandlerType = typeof(CreateUserEndpoint)
|
||||
},
|
||||
new Router.Common.Models.EndpointDescriptor
|
||||
{
|
||||
ServiceName = "test-service",
|
||||
Version = "1.0.0",
|
||||
Method = "POST",
|
||||
Path = "/slow",
|
||||
DefaultTimeout = TimeSpan.FromSeconds(60),
|
||||
HandlerType = typeof(SlowEndpoint)
|
||||
},
|
||||
new Router.Common.Models.EndpointDescriptor
|
||||
{
|
||||
ServiceName = "test-service",
|
||||
Version = "1.0.0",
|
||||
Method = "POST",
|
||||
Path = "/fail",
|
||||
DefaultTimeout = TimeSpan.FromSeconds(30),
|
||||
HandlerType = typeof(FailEndpoint)
|
||||
},
|
||||
new Router.Common.Models.EndpointDescriptor
|
||||
{
|
||||
ServiceName = "test-service",
|
||||
Version = "1.0.0",
|
||||
Method = "POST",
|
||||
Path = "/stream",
|
||||
DefaultTimeout = TimeSpan.FromSeconds(30),
|
||||
SupportsStreaming = true,
|
||||
HandlerType = typeof(StreamEndpoint)
|
||||
},
|
||||
new Router.Common.Models.EndpointDescriptor
|
||||
{
|
||||
ServiceName = "test-service",
|
||||
Version = "1.0.0",
|
||||
Method = "DELETE",
|
||||
Path = "/admin/reset",
|
||||
DefaultTimeout = TimeSpan.FromSeconds(30),
|
||||
RequiringClaims =
|
||||
[
|
||||
new Router.Common.Models.ClaimRequirement { Type = "admin" },
|
||||
new Router.Common.Models.ClaimRequirement { Type = "write" }
|
||||
],
|
||||
HandlerType = typeof(AdminResetEndpoint)
|
||||
},
|
||||
new Router.Common.Models.EndpointDescriptor
|
||||
{
|
||||
ServiceName = "test-service",
|
||||
Version = "1.0.0",
|
||||
Method = "GET",
|
||||
Path = "/quick",
|
||||
DefaultTimeout = TimeSpan.FromSeconds(5),
|
||||
HandlerType = typeof(QuickEndpoint)
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1,122 @@
|
||||
using StellaOps.Microservice;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for path matching and routing.
|
||||
/// </summary>
|
||||
[Collection("Microservice Integration")]
|
||||
public sealed class PathMatchingIntegrationTests
|
||||
{
|
||||
private readonly MicroserviceIntegrationFixture _fixture;
|
||||
|
||||
public PathMatchingIntegrationTests(MicroserviceIntegrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
#region Exact Path Matching Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData("POST", "/echo")]
|
||||
[InlineData("POST", "/users")]
|
||||
[InlineData("POST", "/slow")]
|
||||
[InlineData("POST", "/fail")]
|
||||
[InlineData("POST", "/stream")]
|
||||
[InlineData("DELETE", "/admin/reset")]
|
||||
[InlineData("GET", "/quick")]
|
||||
public void PathMatching_ExactPaths_MatchCorrectly(string method, string path)
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
var found = registry.TryMatch(method, path, out var match);
|
||||
|
||||
// Assert
|
||||
found.Should().BeTrue();
|
||||
match.Should().NotBeNull();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Parameterized Path Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData("/users/123", "/users/{userId}")]
|
||||
[InlineData("/users/abc-def", "/users/{userId}")]
|
||||
[InlineData("/users/user_001", "/users/{userId}")]
|
||||
public void PathMatching_ParameterizedPaths_MatchCorrectly(string requestPath, string expectedPattern)
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
var found = registry.TryMatch("GET", requestPath, out var match);
|
||||
|
||||
// Assert
|
||||
found.Should().BeTrue();
|
||||
match!.Endpoint.Path.Should().Be(expectedPattern);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PathMatching_PostUsersPath_MatchesCreateEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
var found = registry.TryMatch("POST", "/users", out var match);
|
||||
|
||||
// Assert
|
||||
found.Should().BeTrue();
|
||||
match!.Endpoint.HandlerType.Should().Be(typeof(CreateUserEndpoint));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Non-Matching Path Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "/nonexistent")]
|
||||
[InlineData("POST", "/unknown/path")]
|
||||
[InlineData("PUT", "/echo")] // Wrong method
|
||||
[InlineData("GET", "/admin/reset")] // Wrong method
|
||||
public void PathMatching_NonMatchingPaths_ReturnFalse(string method, string path)
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
var found = registry.TryMatch(method, path, out var match);
|
||||
|
||||
// Assert
|
||||
found.Should().BeFalse();
|
||||
match.Should().BeNull();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Method Matching Tests
|
||||
|
||||
[Fact]
|
||||
public void PathMatching_SamePathDifferentMethods_MatchCorrectEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.EndpointRegistry;
|
||||
|
||||
// Act
|
||||
registry.TryMatch("POST", "/users", out var postMatch);
|
||||
registry.TryMatch("GET", "/users/123", out var getMatch);
|
||||
|
||||
// Assert
|
||||
postMatch.Should().NotBeNull();
|
||||
postMatch!.Endpoint.HandlerType.Should().Be(typeof(CreateUserEndpoint));
|
||||
|
||||
getMatch.Should().NotBeNull();
|
||||
getMatch!.Endpoint.HandlerType.Should().Be(typeof(GetUserEndpoint));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Microservice;
|
||||
using StellaOps.Router.Common.Abstractions;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
using StellaOps.Router.Transport.InMemory;
|
||||
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for service registration and DI container.
|
||||
/// </summary>
|
||||
[Collection("Microservice Integration")]
|
||||
public sealed class ServiceRegistrationIntegrationTests
|
||||
{
|
||||
private readonly MicroserviceIntegrationFixture _fixture;
|
||||
|
||||
public ServiceRegistrationIntegrationTests(MicroserviceIntegrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
#region Core Services Tests
|
||||
|
||||
[Fact]
|
||||
public void Services_MicroserviceOptionsAreRegistered()
|
||||
{
|
||||
// Act
|
||||
var options = _fixture.Services.GetService<StellaMicroserviceOptions>();
|
||||
|
||||
// Assert
|
||||
options.Should().NotBeNull();
|
||||
options!.ServiceName.Should().Be("test-service");
|
||||
options.Version.Should().Be("1.0.0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Services_EndpointRegistryIsRegistered()
|
||||
{
|
||||
// Act
|
||||
var registry = _fixture.Services.GetService<IEndpointRegistry>();
|
||||
|
||||
// Assert
|
||||
registry.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Services_ConnectionManagerIsRegistered()
|
||||
{
|
||||
// Act
|
||||
var connectionManager = _fixture.Services.GetService<IRouterConnectionManager>();
|
||||
|
||||
// Assert
|
||||
connectionManager.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Services_RequestDispatcherIsRegistered()
|
||||
{
|
||||
// Act
|
||||
var dispatcher = _fixture.Services.GetService<RequestDispatcher>();
|
||||
|
||||
// Assert
|
||||
dispatcher.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Services_EndpointDiscoveryServiceIsRegistered()
|
||||
{
|
||||
// Act
|
||||
var discoveryService = _fixture.Services.GetService<IEndpointDiscoveryService>();
|
||||
|
||||
// Assert
|
||||
discoveryService.Should().NotBeNull();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Transport Services Tests
|
||||
|
||||
[Fact]
|
||||
public void Services_TransportClientIsRegistered()
|
||||
{
|
||||
// Act
|
||||
var client = _fixture.Services.GetService<ITransportClient>();
|
||||
|
||||
// Assert
|
||||
client.Should().NotBeNull();
|
||||
client.Should().BeOfType<InMemoryTransportClient>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Services_TransportServerIsRegistered()
|
||||
{
|
||||
// Act
|
||||
var server = _fixture.Services.GetService<ITransportServer>();
|
||||
|
||||
// Assert
|
||||
server.Should().NotBeNull();
|
||||
server.Should().BeOfType<InMemoryTransportServer>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Services_InMemoryConnectionRegistryIsRegistered()
|
||||
{
|
||||
// Act
|
||||
var registry = _fixture.Services.GetService<InMemoryConnectionRegistry>();
|
||||
|
||||
// Assert
|
||||
registry.Should().NotBeNull();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Endpoint Handler Tests
|
||||
|
||||
[Fact]
|
||||
public void Services_EndpointHandlersAreRegistered()
|
||||
{
|
||||
// Act
|
||||
using var scope = _fixture.Services.CreateScope();
|
||||
var echoEndpoint = scope.ServiceProvider.GetService<EchoEndpoint>();
|
||||
var getUserEndpoint = scope.ServiceProvider.GetService<GetUserEndpoint>();
|
||||
var createUserEndpoint = scope.ServiceProvider.GetService<CreateUserEndpoint>();
|
||||
|
||||
// Assert
|
||||
echoEndpoint.Should().NotBeNull();
|
||||
getUserEndpoint.Should().NotBeNull();
|
||||
createUserEndpoint.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Services_EndpointHandlersAreScopedInstances()
|
||||
{
|
||||
// Act
|
||||
using var scope1 = _fixture.Services.CreateScope();
|
||||
using var scope2 = _fixture.Services.CreateScope();
|
||||
|
||||
var echo1 = scope1.ServiceProvider.GetService<EchoEndpoint>();
|
||||
var echo2 = scope2.ServiceProvider.GetService<EchoEndpoint>();
|
||||
|
||||
// Assert - Scoped services should be different instances
|
||||
echo1.Should().NotBeSameAs(echo2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Singleton Services Tests
|
||||
|
||||
[Fact]
|
||||
public void Services_SingletonServicesAreSameInstance()
|
||||
{
|
||||
// Act
|
||||
var registry1 = _fixture.Services.GetService<IEndpointRegistry>();
|
||||
var registry2 = _fixture.Services.GetService<IEndpointRegistry>();
|
||||
|
||||
var connectionManager1 = _fixture.Services.GetService<IRouterConnectionManager>();
|
||||
var connectionManager2 = _fixture.Services.GetService<IRouterConnectionManager>();
|
||||
|
||||
// Assert
|
||||
registry1.Should().BeSameAs(registry2);
|
||||
connectionManager1.Should().BeSameAs(connectionManager2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<NoWarn>$(NoWarn);CA2255</NoWarn>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>StellaOps.Router.Integration.Tests</RootNamespace>
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<Using Include="FluentAssertions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Common\StellaOps.Router.Common.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Config\StellaOps.Router.Config.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Transport.InMemory\StellaOps.Router.Transport.InMemory.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Microservice\StellaOps.Microservice.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Router.Testing\StellaOps.Router.Testing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,63 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Router.Common.Abstractions;
|
||||
using StellaOps.Router.Integration.Tests.Fixtures;
|
||||
using StellaOps.Router.Transport.InMemory;
|
||||
|
||||
namespace StellaOps.Router.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for transport layer.
|
||||
/// </summary>
|
||||
[Collection("Microservice Integration")]
|
||||
public sealed class TransportIntegrationTests
|
||||
{
|
||||
private readonly MicroserviceIntegrationFixture _fixture;
|
||||
|
||||
public TransportIntegrationTests(MicroserviceIntegrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
#region InMemory Transport Tests
|
||||
|
||||
[Fact]
|
||||
public void Transport_ClientIsRegistered()
|
||||
{
|
||||
// Arrange & Act
|
||||
var client = _fixture.Services.GetService<ITransportClient>();
|
||||
|
||||
// Assert
|
||||
client.Should().NotBeNull();
|
||||
client.Should().BeOfType<InMemoryTransportClient>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transport_ConnectionRegistryIsShared()
|
||||
{
|
||||
// Arrange
|
||||
var registry = _fixture.Services.GetService<InMemoryConnectionRegistry>();
|
||||
|
||||
// Act & Assert
|
||||
registry.Should().NotBeNull();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Connection Lifecycle Tests
|
||||
|
||||
[Fact]
|
||||
public void Transport_ConnectionIsEstablished()
|
||||
{
|
||||
// Arrange
|
||||
var connectionManager = _fixture.ConnectionManager;
|
||||
|
||||
// Act
|
||||
var connections = connectionManager.Connections;
|
||||
|
||||
// Assert
|
||||
connections.Should().NotBeEmpty();
|
||||
connections.First().Instance.Should().NotBeNull();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user