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,270 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using StellaOps.Gateway.WebService.Authorization;
|
||||
using StellaOps.Router.Common.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Gateway.WebService.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="AuthorityClaimsRefreshService"/>.
|
||||
/// </summary>
|
||||
public sealed class AuthorityClaimsRefreshServiceTests
|
||||
{
|
||||
private readonly Mock<IAuthorityClaimsProvider> _claimsProviderMock;
|
||||
private readonly Mock<IEffectiveClaimsStore> _claimsStoreMock;
|
||||
private readonly AuthorityConnectionOptions _options;
|
||||
|
||||
public AuthorityClaimsRefreshServiceTests()
|
||||
{
|
||||
_claimsProviderMock = new Mock<IAuthorityClaimsProvider>();
|
||||
_claimsStoreMock = new Mock<IEffectiveClaimsStore>();
|
||||
_options = new AuthorityConnectionOptions
|
||||
{
|
||||
AuthorityUrl = "http://authority.local",
|
||||
Enabled = true,
|
||||
RefreshInterval = TimeSpan.FromMilliseconds(100),
|
||||
WaitForAuthorityOnStartup = false,
|
||||
StartupTimeout = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
|
||||
_claimsProviderMock.Setup(p => p.GetOverridesAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new Dictionary<EndpointKey, IReadOnlyList<ClaimRequirement>>());
|
||||
}
|
||||
|
||||
private AuthorityClaimsRefreshService CreateService()
|
||||
{
|
||||
return new AuthorityClaimsRefreshService(
|
||||
_claimsProviderMock.Object,
|
||||
_claimsStoreMock.Object,
|
||||
Options.Create(_options),
|
||||
NullLogger<AuthorityClaimsRefreshService>.Instance);
|
||||
}
|
||||
|
||||
#region ExecuteAsync Tests - Disabled
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WhenDisabled_DoesNotFetchClaims()
|
||||
{
|
||||
// Arrange
|
||||
_options.Enabled = false;
|
||||
var service = CreateService();
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
// Act
|
||||
await service.StartAsync(cts.Token);
|
||||
await Task.Delay(50);
|
||||
await service.StopAsync(cts.Token);
|
||||
|
||||
// Assert
|
||||
_claimsProviderMock.Verify(
|
||||
p => p.GetOverridesAsync(It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WhenNoAuthorityUrl_DoesNotFetchClaims()
|
||||
{
|
||||
// Arrange
|
||||
_options.AuthorityUrl = string.Empty;
|
||||
var service = CreateService();
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
// Act
|
||||
await service.StartAsync(cts.Token);
|
||||
await Task.Delay(50);
|
||||
await service.StopAsync(cts.Token);
|
||||
|
||||
// Assert
|
||||
_claimsProviderMock.Verify(
|
||||
p => p.GetOverridesAsync(It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ExecuteAsync Tests - Enabled
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WhenEnabled_FetchesClaims()
|
||||
{
|
||||
// Arrange
|
||||
var service = CreateService();
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
// Act
|
||||
await service.StartAsync(cts.Token);
|
||||
await Task.Delay(50);
|
||||
await cts.CancelAsync();
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
_claimsProviderMock.Verify(
|
||||
p => p.GetOverridesAsync(It.IsAny<CancellationToken>()),
|
||||
Times.AtLeastOnce);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_UpdatesStoreWithOverrides()
|
||||
{
|
||||
// Arrange
|
||||
var key = EndpointKey.Create("service", "GET", "/api/test");
|
||||
var overrides = new Dictionary<EndpointKey, IReadOnlyList<ClaimRequirement>>
|
||||
{
|
||||
[key] = [new ClaimRequirement { Type = "role", Value = "admin" }]
|
||||
};
|
||||
_claimsProviderMock.Setup(p => p.GetOverridesAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(overrides);
|
||||
|
||||
var service = CreateService();
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
// Act
|
||||
await service.StartAsync(cts.Token);
|
||||
await Task.Delay(50);
|
||||
await cts.CancelAsync();
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
_claimsStoreMock.Verify(
|
||||
s => s.UpdateFromAuthority(It.Is<IReadOnlyDictionary<EndpointKey, IReadOnlyList<ClaimRequirement>>>(
|
||||
d => d.ContainsKey(key))),
|
||||
Times.AtLeastOnce);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ExecuteAsync Tests - Wait for Authority
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WaitForAuthority_FetchesOnStartup()
|
||||
{
|
||||
// Arrange
|
||||
_options.WaitForAuthorityOnStartup = true;
|
||||
_options.StartupTimeout = TimeSpan.FromMilliseconds(500);
|
||||
|
||||
// Authority is immediately available
|
||||
_claimsProviderMock.Setup(p => p.IsAvailable).Returns(true);
|
||||
|
||||
var fetchCalled = false;
|
||||
_claimsProviderMock.Setup(p => p.GetOverridesAsync(It.IsAny<CancellationToken>()))
|
||||
.Callback(() => fetchCalled = true)
|
||||
.ReturnsAsync(new Dictionary<EndpointKey, IReadOnlyList<ClaimRequirement>>());
|
||||
|
||||
var service = CreateService();
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
// Act
|
||||
await service.StartAsync(cts.Token);
|
||||
await Task.Delay(100);
|
||||
await cts.CancelAsync();
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
|
||||
// Assert - fetch was called during startup
|
||||
fetchCalled.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WaitForAuthority_StopsAfterTimeout()
|
||||
{
|
||||
// Arrange
|
||||
_options.WaitForAuthorityOnStartup = true;
|
||||
_options.StartupTimeout = TimeSpan.FromMilliseconds(100);
|
||||
|
||||
_claimsProviderMock.Setup(p => p.IsAvailable).Returns(false);
|
||||
|
||||
var service = CreateService();
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
// Act - should not block forever
|
||||
var startTask = service.StartAsync(cts.Token);
|
||||
await Task.Delay(300);
|
||||
await cts.CancelAsync();
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
|
||||
// Assert - should complete even if Authority never becomes available
|
||||
startTask.IsCompleted.Should().BeTrue();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Push Notification Tests
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithPushNotifications_SubscribesToEvent()
|
||||
{
|
||||
// Arrange
|
||||
_options.UseAuthorityPushNotifications = true;
|
||||
var service = CreateService();
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
// Act
|
||||
await service.StartAsync(cts.Token);
|
||||
await Task.Delay(50);
|
||||
await cts.CancelAsync();
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
|
||||
// Assert - verify event subscription by checking it doesn't throw
|
||||
_claimsProviderMock.VerifyAdd(
|
||||
p => p.OverridesChanged += It.IsAny<EventHandler<ClaimsOverrideChangedEventArgs>>(),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Dispose_WithPushNotifications_UnsubscribesFromEvent()
|
||||
{
|
||||
// Arrange
|
||||
_options.UseAuthorityPushNotifications = true;
|
||||
var service = CreateService();
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
await service.StartAsync(cts.Token);
|
||||
await Task.Delay(50);
|
||||
|
||||
// Act
|
||||
await cts.CancelAsync();
|
||||
service.Dispose();
|
||||
|
||||
// Assert
|
||||
_claimsProviderMock.VerifyRemove(
|
||||
p => p.OverridesChanged -= It.IsAny<EventHandler<ClaimsOverrideChangedEventArgs>>(),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Error Handling Tests
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ProviderThrows_ContinuesRefreshLoop()
|
||||
{
|
||||
// Arrange
|
||||
var callCount = 0;
|
||||
_claimsProviderMock.Setup(p => p.GetOverridesAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(() =>
|
||||
{
|
||||
callCount++;
|
||||
if (callCount == 1)
|
||||
{
|
||||
throw new HttpRequestException("Test error");
|
||||
}
|
||||
return new Dictionary<EndpointKey, IReadOnlyList<ClaimRequirement>>();
|
||||
});
|
||||
|
||||
var service = CreateService();
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
// Act
|
||||
await service.StartAsync(cts.Token);
|
||||
await Task.Delay(250); // Wait for at least 2 refresh cycles
|
||||
await cts.CancelAsync();
|
||||
await service.StopAsync(CancellationToken.None);
|
||||
|
||||
// Assert - should have continued after error
|
||||
callCount.Should().BeGreaterThan(1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user