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,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
</PropertyGroup>
|
||||
<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.DependencyInjection" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Transport.Udp\StellaOps.Router.Transport.Udp.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Common\StellaOps.Router.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,211 @@
|
||||
using FluentAssertions;
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
using StellaOps.Router.Transport.Udp;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Router.Transport.Udp.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="UdpFrameProtocol"/>.
|
||||
/// </summary>
|
||||
public sealed class UdpFrameProtocolTests
|
||||
{
|
||||
#region ParseFrame Tests
|
||||
|
||||
[Fact]
|
||||
public void ParseFrame_ValidFrame_ParsesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var correlationId = Guid.NewGuid();
|
||||
var payload = new byte[] { 1, 2, 3, 4, 5 };
|
||||
var data = CreateFrameData(FrameType.Request, correlationId, payload);
|
||||
|
||||
// Act
|
||||
var frame = UdpFrameProtocol.ParseFrame(data);
|
||||
|
||||
// Assert
|
||||
frame.Type.Should().Be(FrameType.Request);
|
||||
frame.CorrelationId.Should().Be(correlationId.ToString("N"));
|
||||
frame.Payload.ToArray().Should().Equal(payload);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseFrame_EmptyPayload_ParsesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var correlationId = Guid.NewGuid();
|
||||
var data = CreateFrameData(FrameType.Heartbeat, correlationId, []);
|
||||
|
||||
// Act
|
||||
var frame = UdpFrameProtocol.ParseFrame(data);
|
||||
|
||||
// Assert
|
||||
frame.Type.Should().Be(FrameType.Heartbeat);
|
||||
frame.CorrelationId.Should().Be(correlationId.ToString("N"));
|
||||
frame.Payload.Length.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseFrame_DataTooSmall_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var data = new byte[10]; // Less than header size (17 bytes)
|
||||
|
||||
// Act
|
||||
var action = () => UdpFrameProtocol.ParseFrame(data);
|
||||
|
||||
// Assert
|
||||
action.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*too small*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseFrame_MinimumHeaderSize_Works()
|
||||
{
|
||||
// Arrange - exactly header size (17 bytes)
|
||||
var correlationId = Guid.NewGuid();
|
||||
var data = CreateFrameData(FrameType.Cancel, correlationId, []);
|
||||
|
||||
// Act
|
||||
var frame = UdpFrameProtocol.ParseFrame(data);
|
||||
|
||||
// Assert
|
||||
frame.Type.Should().Be(FrameType.Cancel);
|
||||
frame.Payload.Length.Should().Be(0);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(FrameType.Request)]
|
||||
[InlineData(FrameType.Response)]
|
||||
[InlineData(FrameType.Hello)]
|
||||
[InlineData(FrameType.Heartbeat)]
|
||||
[InlineData(FrameType.Cancel)]
|
||||
public void ParseFrame_AllFrameTypes_ParseCorrectly(FrameType frameType)
|
||||
{
|
||||
// Arrange
|
||||
var correlationId = Guid.NewGuid();
|
||||
var data = CreateFrameData(frameType, correlationId, [0xAB, 0xCD]);
|
||||
|
||||
// Act
|
||||
var frame = UdpFrameProtocol.ParseFrame(data);
|
||||
|
||||
// Assert
|
||||
frame.Type.Should().Be(frameType);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SerializeFrame Tests
|
||||
|
||||
[Fact]
|
||||
public void SerializeFrame_ValidFrame_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var correlationId = Guid.NewGuid();
|
||||
var frame = new Frame
|
||||
{
|
||||
Type = FrameType.Response,
|
||||
CorrelationId = correlationId.ToString("N"),
|
||||
Payload = new byte[] { 10, 20, 30 }
|
||||
};
|
||||
|
||||
// Act
|
||||
var data = UdpFrameProtocol.SerializeFrame(frame);
|
||||
|
||||
// Assert
|
||||
data.Length.Should().Be(17 + 3); // Header + payload
|
||||
data[0].Should().Be((byte)FrameType.Response);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializeFrame_EmptyPayload_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var frame = new Frame
|
||||
{
|
||||
Type = FrameType.Hello,
|
||||
CorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Payload = ReadOnlyMemory<byte>.Empty
|
||||
};
|
||||
|
||||
// Act
|
||||
var data = UdpFrameProtocol.SerializeFrame(frame);
|
||||
|
||||
// Assert
|
||||
data.Length.Should().Be(17); // Header only
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializeFrame_NullCorrelationId_GeneratesNewGuid()
|
||||
{
|
||||
// Arrange
|
||||
var frame = new Frame
|
||||
{
|
||||
Type = FrameType.Request,
|
||||
CorrelationId = null,
|
||||
Payload = new byte[] { 1 }
|
||||
};
|
||||
|
||||
// Act
|
||||
var data = UdpFrameProtocol.SerializeFrame(frame);
|
||||
|
||||
// Assert
|
||||
data.Length.Should().Be(18);
|
||||
// Correlation ID bytes should be non-zero (not all zeros)
|
||||
var correlationBytes = data.AsSpan(1, 16);
|
||||
correlationBytes.ToArray().Should().NotBeEquivalentTo(new byte[16]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializeFrame_RoundTrip_PreservesData()
|
||||
{
|
||||
// Arrange
|
||||
var correlationId = Guid.NewGuid();
|
||||
var payload = new byte[] { 0xFF, 0xFE, 0xFD, 0xFC };
|
||||
var originalFrame = new Frame
|
||||
{
|
||||
Type = FrameType.Request,
|
||||
CorrelationId = correlationId.ToString("N"),
|
||||
Payload = payload
|
||||
};
|
||||
|
||||
// Act
|
||||
var serialized = UdpFrameProtocol.SerializeFrame(originalFrame);
|
||||
var parsedFrame = UdpFrameProtocol.ParseFrame(serialized);
|
||||
|
||||
// Assert
|
||||
parsedFrame.Type.Should().Be(originalFrame.Type);
|
||||
parsedFrame.CorrelationId.Should().Be(originalFrame.CorrelationId);
|
||||
parsedFrame.Payload.ToArray().Should().Equal(payload);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetHeaderSize Tests
|
||||
|
||||
[Fact]
|
||||
public void GetHeaderSize_ReturnsExpectedValue()
|
||||
{
|
||||
// Act
|
||||
var headerSize = UdpFrameProtocol.GetHeaderSize();
|
||||
|
||||
// Assert
|
||||
headerSize.Should().Be(17); // 1 byte frame type + 16 bytes GUID
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private static byte[] CreateFrameData(FrameType frameType, Guid correlationId, byte[] payload)
|
||||
{
|
||||
var buffer = new byte[17 + payload.Length];
|
||||
buffer[0] = (byte)frameType;
|
||||
correlationId.TryWriteBytes(buffer.AsSpan(1, 16));
|
||||
payload.CopyTo(buffer.AsSpan(17));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
using StellaOps.Router.Transport.Udp;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Router.Transport.Udp.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="UdpTransportClient"/>.
|
||||
/// </summary>
|
||||
public sealed class UdpTransportClientTests
|
||||
{
|
||||
#region ConnectAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task ConnectAsync_WithNoHost_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions
|
||||
{
|
||||
Host = null,
|
||||
Port = 5000
|
||||
});
|
||||
await using var client = new UdpTransportClient(options, NullLogger<UdpTransportClient>.Instance);
|
||||
|
||||
var instance = new InstanceDescriptor
|
||||
{
|
||||
InstanceId = "test-instance",
|
||||
ServiceName = "test-service",
|
||||
Version = "1.0.0",
|
||||
Region = "local"
|
||||
};
|
||||
|
||||
// Act
|
||||
var action = () => client.ConnectAsync(instance, [], CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await action.Should().ThrowAsync<InvalidOperationException>()
|
||||
.WithMessage("*Host is not configured*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConnectAsync_AfterDispose_ThrowsObjectDisposedException()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 5000
|
||||
});
|
||||
var client = new UdpTransportClient(options, NullLogger<UdpTransportClient>.Instance);
|
||||
await client.DisposeAsync();
|
||||
|
||||
var instance = new InstanceDescriptor
|
||||
{
|
||||
InstanceId = "test-instance",
|
||||
ServiceName = "test-service",
|
||||
Version = "1.0.0",
|
||||
Region = "local"
|
||||
};
|
||||
|
||||
// Act
|
||||
var action = () => client.ConnectAsync(instance, [], CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await action.Should().ThrowAsync<ObjectDisposedException>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SendStreamingAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task SendStreamingAsync_ThrowsNotSupportedException()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 5000
|
||||
});
|
||||
await using var client = new UdpTransportClient(options, NullLogger<UdpTransportClient>.Instance);
|
||||
|
||||
var connection = new ConnectionState
|
||||
{
|
||||
ConnectionId = "test",
|
||||
Instance = new InstanceDescriptor
|
||||
{
|
||||
InstanceId = "test",
|
||||
ServiceName = "test",
|
||||
Version = "1.0.0",
|
||||
Region = "local"
|
||||
},
|
||||
Status = InstanceHealthStatus.Healthy,
|
||||
LastHeartbeatUtc = DateTime.UtcNow,
|
||||
TransportType = TransportType.Udp
|
||||
};
|
||||
|
||||
var frame = new Frame
|
||||
{
|
||||
Type = FrameType.Request,
|
||||
CorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Payload = ReadOnlyMemory<byte>.Empty
|
||||
};
|
||||
|
||||
using var requestBody = new MemoryStream();
|
||||
var limits = new PayloadLimits();
|
||||
|
||||
// Act
|
||||
var action = () => client.SendStreamingAsync(
|
||||
connection,
|
||||
frame,
|
||||
requestBody,
|
||||
_ => Task.CompletedTask,
|
||||
limits,
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await action.Should().ThrowAsync<NotSupportedException>()
|
||||
.WithMessage("*UDP transport does not support streaming*");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CancelAllInflight Tests
|
||||
|
||||
[Fact]
|
||||
public async Task CancelAllInflight_WithNoInflight_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 5000
|
||||
});
|
||||
await using var client = new UdpTransportClient(options, NullLogger<UdpTransportClient>.Instance);
|
||||
|
||||
// Act
|
||||
var action = () => client.CancelAllInflight("Test shutdown");
|
||||
|
||||
// Assert
|
||||
action.Should().NotThrow();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DisposeAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_CanBeCalledMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 5000
|
||||
});
|
||||
var client = new UdpTransportClient(options, NullLogger<UdpTransportClient>.Instance);
|
||||
|
||||
// Act
|
||||
var action = async () =>
|
||||
{
|
||||
await client.DisposeAsync();
|
||||
await client.DisposeAsync();
|
||||
await client.DisposeAsync();
|
||||
};
|
||||
|
||||
// Assert
|
||||
await action.Should().NotThrowAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Tests
|
||||
|
||||
[Fact]
|
||||
public async Task OnRequestReceived_CanBeSubscribed()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 5000
|
||||
});
|
||||
await using var client = new UdpTransportClient(options, NullLogger<UdpTransportClient>.Instance);
|
||||
|
||||
// Act
|
||||
client.OnRequestReceived += (frame, ct) => Task.FromResult(new Frame
|
||||
{
|
||||
Type = FrameType.Response,
|
||||
CorrelationId = frame.CorrelationId,
|
||||
Payload = ReadOnlyMemory<byte>.Empty
|
||||
});
|
||||
|
||||
// Assert - no exception
|
||||
client.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OnCancelReceived_CanBeSubscribed()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 5000
|
||||
});
|
||||
await using var client = new UdpTransportClient(options, NullLogger<UdpTransportClient>.Instance);
|
||||
|
||||
Guid? receivedCorrelationId = null;
|
||||
|
||||
// Act
|
||||
client.OnCancelReceived += (correlationId, reason) =>
|
||||
{
|
||||
receivedCorrelationId = correlationId;
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
// Assert - no exception
|
||||
client.Should().NotBeNull();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SendCancelAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task SendCancelAsync_AfterDispose_ThrowsObjectDisposedException()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 5000
|
||||
});
|
||||
var client = new UdpTransportClient(options, NullLogger<UdpTransportClient>.Instance);
|
||||
await client.DisposeAsync();
|
||||
|
||||
var connection = new ConnectionState
|
||||
{
|
||||
ConnectionId = "test",
|
||||
Instance = new InstanceDescriptor
|
||||
{
|
||||
InstanceId = "test",
|
||||
ServiceName = "test",
|
||||
Version = "1.0.0",
|
||||
Region = "local"
|
||||
},
|
||||
Status = InstanceHealthStatus.Healthy,
|
||||
LastHeartbeatUtc = DateTime.UtcNow,
|
||||
TransportType = TransportType.Udp
|
||||
};
|
||||
|
||||
// Act
|
||||
var action = () => client.SendCancelAsync(connection, Guid.NewGuid(), "Test");
|
||||
|
||||
// Assert
|
||||
await action.Should().ThrowAsync<ObjectDisposedException>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SendRequestAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task SendRequestAsync_AfterDispose_ThrowsObjectDisposedException()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 5000
|
||||
});
|
||||
var client = new UdpTransportClient(options, NullLogger<UdpTransportClient>.Instance);
|
||||
await client.DisposeAsync();
|
||||
|
||||
var connection = new ConnectionState
|
||||
{
|
||||
ConnectionId = "test",
|
||||
Instance = new InstanceDescriptor
|
||||
{
|
||||
InstanceId = "test",
|
||||
ServiceName = "test",
|
||||
Version = "1.0.0",
|
||||
Region = "local"
|
||||
},
|
||||
Status = InstanceHealthStatus.Healthy,
|
||||
LastHeartbeatUtc = DateTime.UtcNow,
|
||||
TransportType = TransportType.Udp
|
||||
};
|
||||
|
||||
var frame = new Frame
|
||||
{
|
||||
Type = FrameType.Request,
|
||||
CorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Payload = ReadOnlyMemory<byte>.Empty
|
||||
};
|
||||
|
||||
// Act
|
||||
var action = () => client.SendRequestAsync(connection, frame, TimeSpan.FromSeconds(5), CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await action.Should().ThrowAsync<ObjectDisposedException>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
using System.Net;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Router.Common.Abstractions;
|
||||
using StellaOps.Router.Transport.Udp;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Router.Transport.Udp.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="UdpTransportOptions"/>.
|
||||
/// </summary>
|
||||
public sealed class UdpTransportOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultOptions_HaveCorrectValues()
|
||||
{
|
||||
// Arrange & Act
|
||||
var options = new UdpTransportOptions();
|
||||
|
||||
// Assert
|
||||
options.BindAddress.Should().Be(IPAddress.Any);
|
||||
options.Port.Should().Be(5102);
|
||||
options.Host.Should().BeNull();
|
||||
options.MaxDatagramSize.Should().Be(8192);
|
||||
options.DefaultTimeout.Should().Be(TimeSpan.FromSeconds(5));
|
||||
options.AllowBroadcast.Should().BeFalse();
|
||||
options.ReceiveBufferSize.Should().Be(64 * 1024);
|
||||
options.SendBufferSize.Should().Be(64 * 1024);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Options_CanBeModified()
|
||||
{
|
||||
// Arrange
|
||||
var options = new UdpTransportOptions
|
||||
{
|
||||
BindAddress = IPAddress.Loopback,
|
||||
Port = 9999,
|
||||
Host = "example.com",
|
||||
MaxDatagramSize = 4096,
|
||||
DefaultTimeout = TimeSpan.FromSeconds(10),
|
||||
AllowBroadcast = true,
|
||||
ReceiveBufferSize = 32 * 1024,
|
||||
SendBufferSize = 16 * 1024
|
||||
};
|
||||
|
||||
// Assert
|
||||
options.BindAddress.Should().Be(IPAddress.Loopback);
|
||||
options.Port.Should().Be(9999);
|
||||
options.Host.Should().Be("example.com");
|
||||
options.MaxDatagramSize.Should().Be(4096);
|
||||
options.DefaultTimeout.Should().Be(TimeSpan.FromSeconds(10));
|
||||
options.AllowBroadcast.Should().BeTrue();
|
||||
options.ReceiveBufferSize.Should().Be(32 * 1024);
|
||||
options.SendBufferSize.Should().Be(16 * 1024);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="PayloadTooLargeException"/>.
|
||||
/// </summary>
|
||||
public sealed class PayloadTooLargeExceptionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_SetsProperties()
|
||||
{
|
||||
// Arrange
|
||||
var actualSize = 10000;
|
||||
var maxSize = 8192;
|
||||
|
||||
// Act
|
||||
var exception = new PayloadTooLargeException(actualSize, maxSize);
|
||||
|
||||
// Assert
|
||||
exception.ActualSize.Should().Be(actualSize);
|
||||
exception.MaxSize.Should().Be(maxSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_SetsMessage()
|
||||
{
|
||||
// Arrange
|
||||
var actualSize = 10000;
|
||||
var maxSize = 8192;
|
||||
|
||||
// Act
|
||||
var exception = new PayloadTooLargeException(actualSize, maxSize);
|
||||
|
||||
// Assert
|
||||
exception.Message.Should().Contain("10000");
|
||||
exception.Message.Should().Contain("8192");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Exception_IsExceptionType()
|
||||
{
|
||||
// Arrange & Act
|
||||
var exception = new PayloadTooLargeException(100, 50);
|
||||
|
||||
// Assert
|
||||
exception.Should().BeAssignableTo<Exception>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="ServiceCollectionExtensions"/>.
|
||||
/// </summary>
|
||||
public sealed class UdpServiceCollectionExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void AddUdpTransportServer_RegistersServices()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
|
||||
// Act
|
||||
services.AddUdpTransportServer(options =>
|
||||
{
|
||||
options.Port = 5102;
|
||||
});
|
||||
|
||||
// Assert
|
||||
var provider = services.BuildServiceProvider();
|
||||
var server = provider.GetService<UdpTransportServer>();
|
||||
var transportServer = provider.GetService<ITransportServer>();
|
||||
|
||||
server.Should().NotBeNull();
|
||||
transportServer.Should().BeSameAs(server);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddUdpTransportServer_WithNullConfigure_Works()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
|
||||
// Act
|
||||
services.AddUdpTransportServer();
|
||||
|
||||
// Assert
|
||||
var provider = services.BuildServiceProvider();
|
||||
var server = provider.GetService<UdpTransportServer>();
|
||||
|
||||
server.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddUdpTransportClient_RegistersServices()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
|
||||
// Act
|
||||
services.AddUdpTransportClient(options =>
|
||||
{
|
||||
options.Host = "localhost";
|
||||
options.Port = 5102;
|
||||
});
|
||||
|
||||
// Assert
|
||||
var provider = services.BuildServiceProvider();
|
||||
var client = provider.GetService<UdpTransportClient>();
|
||||
var transportClient = provider.GetService<ITransportClient>();
|
||||
var microserviceTransport = provider.GetService<IMicroserviceTransport>();
|
||||
|
||||
client.Should().NotBeNull();
|
||||
transportClient.Should().BeSameAs(client);
|
||||
microserviceTransport.Should().BeSameAs(client);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddUdpTransportClient_WithNullConfigure_Works()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
|
||||
// Act
|
||||
services.AddUdpTransportClient();
|
||||
|
||||
// Assert
|
||||
var provider = services.BuildServiceProvider();
|
||||
var client = provider.GetService<UdpTransportClient>();
|
||||
|
||||
client.Should().NotBeNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
using System.Net;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
using StellaOps.Router.Transport.Udp;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Router.Transport.Udp.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="UdpTransportServer"/>.
|
||||
/// </summary>
|
||||
public sealed class UdpTransportServerTests
|
||||
{
|
||||
#region StartAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_StartsListening()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions { Port = 0 }); // Auto-assign port
|
||||
await using var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
|
||||
|
||||
// Act
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
server.ConnectionCount.Should().Be(0);
|
||||
|
||||
// Cleanup
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_AfterDispose_ThrowsObjectDisposedException()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions { Port = 0 });
|
||||
var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
|
||||
await server.DisposeAsync();
|
||||
|
||||
// Act
|
||||
var action = () => server.StartAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
await action.Should().ThrowAsync<ObjectDisposedException>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region StopAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task StopAsync_StopsServer()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions { Port = 0 });
|
||||
await using var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
// Act
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
server.ConnectionCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StopAsync_ClearsConnections()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions { Port = 0 });
|
||||
await using var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
// Act
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
server.GetConnections().Should().BeEmpty();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetConnectionState Tests
|
||||
|
||||
[Fact]
|
||||
public async Task GetConnectionState_UnknownConnection_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions { Port = 0 });
|
||||
await using var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var state = server.GetConnectionState("unknown-connection-id");
|
||||
|
||||
// Assert
|
||||
state.Should().BeNull();
|
||||
|
||||
// Cleanup
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RemoveConnection Tests
|
||||
|
||||
[Fact]
|
||||
public async Task RemoveConnection_UnknownConnection_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions { Port = 0 });
|
||||
await using var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var action = () => server.RemoveConnection("unknown-connection");
|
||||
|
||||
// Assert
|
||||
action.Should().NotThrow();
|
||||
|
||||
// Cleanup
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SendFrameAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task SendFrameAsync_UnknownConnection_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions { Port = 0 });
|
||||
await using var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
var frame = new Frame
|
||||
{
|
||||
Type = FrameType.Response,
|
||||
CorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Payload = ReadOnlyMemory<byte>.Empty
|
||||
};
|
||||
|
||||
// Act
|
||||
var action = () => server.SendFrameAsync("unknown-connection", frame);
|
||||
|
||||
// Assert
|
||||
await action.Should().ThrowAsync<InvalidOperationException>()
|
||||
.WithMessage("*not found*");
|
||||
|
||||
// Cleanup
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendFrameAsync_AfterDispose_ThrowsObjectDisposedException()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions { Port = 0 });
|
||||
var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
|
||||
await server.DisposeAsync();
|
||||
|
||||
var frame = new Frame
|
||||
{
|
||||
Type = FrameType.Response,
|
||||
CorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Payload = ReadOnlyMemory<byte>.Empty
|
||||
};
|
||||
|
||||
// Act
|
||||
var action = () => server.SendFrameAsync("any-connection", frame);
|
||||
|
||||
// Assert
|
||||
await action.Should().ThrowAsync<ObjectDisposedException>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetConnections Tests
|
||||
|
||||
[Fact]
|
||||
public async Task GetConnections_InitiallyEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions { Port = 0 });
|
||||
await using var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var connections = server.GetConnections();
|
||||
|
||||
// Assert
|
||||
connections.Should().BeEmpty();
|
||||
|
||||
// Cleanup
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DisposeAsync Tests
|
||||
|
||||
[Fact]
|
||||
public async Task DisposeAsync_CanBeCalledMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions { Port = 0 });
|
||||
var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var action = async () =>
|
||||
{
|
||||
await server.DisposeAsync();
|
||||
await server.DisposeAsync();
|
||||
await server.DisposeAsync();
|
||||
};
|
||||
|
||||
// Assert
|
||||
await action.Should().NotThrowAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Tests
|
||||
|
||||
[Fact]
|
||||
public async Task OnConnection_EventCanBeSubscribed()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions { Port = 0 });
|
||||
await using var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
|
||||
|
||||
string? receivedConnectionId = null;
|
||||
server.OnConnection += (id, state) => receivedConnectionId = id;
|
||||
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
// Assert - no exception during subscription
|
||||
server.Should().NotBeNull();
|
||||
|
||||
// Cleanup
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OnFrame_EventCanBeSubscribed()
|
||||
{
|
||||
// Arrange
|
||||
var options = Options.Create(new UdpTransportOptions { Port = 0 });
|
||||
await using var server = new UdpTransportServer(options, NullLogger<UdpTransportServer>.Instance);
|
||||
|
||||
Frame? receivedFrame = null;
|
||||
server.OnFrame += (id, frame) => receivedFrame = frame;
|
||||
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
// Assert - no exception during subscription
|
||||
server.Should().NotBeNull();
|
||||
|
||||
// Cleanup
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user