Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -12,6 +12,8 @@ using StellaOps.Router.Transport.InMemory;
|
||||
using StellaOps.Router.Transport.Tls;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Transport.Tcp.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -40,21 +42,24 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
|
||||
#region Connection Failure Scenarios
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Options_MaxReconnectAttempts_DefaultIsTen()
|
||||
{
|
||||
var options = new TcpTransportOptions();
|
||||
options.MaxReconnectAttempts.Should().Be(10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Options_MaxReconnectBackoff_DefaultIsOneMinute()
|
||||
{
|
||||
var options = new TcpTransportOptions();
|
||||
options.MaxReconnectBackoff.Should().Be(TimeSpan.FromMinutes(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Options_ReconnectSettings_CanBeCustomized()
|
||||
{
|
||||
var options = new TcpTransportOptions
|
||||
@@ -71,7 +76,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
|
||||
#region Exponential Backoff Calculation
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(1, 200)] // 2^1 * 100 = 200ms
|
||||
[InlineData(2, 400)] // 2^2 * 100 = 400ms
|
||||
[InlineData(3, 800)] // 2^3 * 100 = 800ms
|
||||
@@ -84,7 +90,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
calculated.Should().Be(expectedMs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Backoff_CappedAtMaximum_WhenExceedsLimit()
|
||||
{
|
||||
var maxBackoff = TimeSpan.FromMinutes(1);
|
||||
@@ -96,7 +103,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
capped.Should().Be(maxBackoff.TotalMilliseconds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Backoff_Sequence_IsMonotonicallyIncreasing()
|
||||
{
|
||||
var maxBackoff = TimeSpan.FromMinutes(1);
|
||||
@@ -118,7 +126,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
|
||||
#region Connection Refused Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Connect_ServerNotListening_ThrowsException()
|
||||
{
|
||||
// Arrange - Stop the listener so connection will be refused
|
||||
@@ -148,7 +157,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
await client.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Connect_InvalidHost_ThrowsException()
|
||||
{
|
||||
var options = new TcpTransportOptions
|
||||
@@ -179,7 +189,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
|
||||
#region Connection Drop Detection
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ServerDropsConnection_ReadReturnsNull()
|
||||
{
|
||||
// This test verifies the frame protocol handles connection drops
|
||||
@@ -205,7 +216,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
|
||||
#region Reconnection State Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ReconnectAttempts_ResetOnSuccessfulConnection()
|
||||
{
|
||||
// This is a behavioral expectation from the implementation:
|
||||
@@ -224,7 +236,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
options.MaxReconnectAttempts.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReconnectionLoop_RespectsMaxAttempts()
|
||||
{
|
||||
// Arrange
|
||||
@@ -244,7 +257,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
|
||||
#region Frame Protocol Connection Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FrameProtocol_ReadFromClosedStream_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -257,7 +271,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
frame.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FrameProtocol_PartialRead_HandlesGracefully()
|
||||
{
|
||||
// Arrange - Create a stream with incomplete frame header
|
||||
@@ -276,7 +291,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
|
||||
#region Timeout Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Connect_Timeout_RespectsTimeoutSetting()
|
||||
{
|
||||
var options = new TcpTransportOptions
|
||||
@@ -319,7 +335,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
|
||||
#region Disposal During Reconnection
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Dispose_DuringPendingConnect_CancelsGracefully()
|
||||
{
|
||||
var options = new TcpTransportOptions
|
||||
@@ -360,7 +377,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
|
||||
#region Socket Error Classification
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SocketException_ConnectionRefused_IsRecoverable()
|
||||
{
|
||||
var ex = new SocketException((int)SocketError.ConnectionRefused);
|
||||
@@ -369,7 +387,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
ex.SocketErrorCode.Should().Be(SocketError.ConnectionRefused);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SocketException_ConnectionReset_IsRecoverable()
|
||||
{
|
||||
var ex = new SocketException((int)SocketError.ConnectionReset);
|
||||
@@ -378,7 +397,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
ex.SocketErrorCode.Should().Be(SocketError.ConnectionReset);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SocketException_NetworkUnreachable_IsRecoverable()
|
||||
{
|
||||
var ex = new SocketException((int)SocketError.NetworkUnreachable);
|
||||
@@ -387,7 +407,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
ex.SocketErrorCode.Should().Be(SocketError.NetworkUnreachable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SocketException_TimedOut_IsRecoverable()
|
||||
{
|
||||
var ex = new SocketException((int)SocketError.TimedOut);
|
||||
@@ -400,7 +421,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
|
||||
#region Multiple Reconnection Cycles
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BackoffSequence_MultipleFullCycles_Deterministic()
|
||||
{
|
||||
// Verify that backoff calculation is deterministic across cycles
|
||||
@@ -429,7 +451,8 @@ public sealed class ConnectionFailureTests : IDisposable
|
||||
|
||||
#region Connection State Tracking
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Client_InitialState_NotConnected()
|
||||
{
|
||||
var options = new TcpTransportOptions
|
||||
@@ -457,21 +480,24 @@ public sealed class TlsConnectionFailureTests
|
||||
{
|
||||
#region TLS-Specific Options
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TlsOptions_MaxReconnectAttempts_DefaultIsTen()
|
||||
{
|
||||
var options = new TlsTransportOptions();
|
||||
options.MaxReconnectAttempts.Should().Be(10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TlsOptions_MaxReconnectBackoff_DefaultIsOneMinute()
|
||||
{
|
||||
var options = new TlsTransportOptions();
|
||||
options.MaxReconnectBackoff.Should().Be(TimeSpan.FromMinutes(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TlsOptions_ReconnectAndSsl_CanBeCombined()
|
||||
{
|
||||
var options = new TlsTransportOptions
|
||||
@@ -492,7 +518,8 @@ public sealed class TlsConnectionFailureTests
|
||||
|
||||
#region TLS Connection Failures
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TlsConnect_InvalidCertificate_ShouldFail()
|
||||
{
|
||||
// TLS connections with invalid certificates should fail
|
||||
@@ -511,7 +538,8 @@ public sealed class TlsConnectionFailureTests
|
||||
options.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TlsBackoff_SameFormulaAsTcp()
|
||||
{
|
||||
// TLS uses the same exponential backoff formula
|
||||
@@ -531,7 +559,8 @@ public sealed class TlsConnectionFailureTests
|
||||
/// </summary>
|
||||
public sealed class InMemoryConnectionFailureTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void InMemoryChannel_NoReconnection_NotApplicable()
|
||||
{
|
||||
// InMemory transport doesn't have network connections
|
||||
@@ -553,7 +582,8 @@ public sealed class InMemoryConnectionFailureTests
|
||||
canWrite.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InMemoryChannel_CompletedWithError_PropagatesError()
|
||||
{
|
||||
using var channel = new InMemoryChannel("error-complete");
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.Text;
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Transport.Tcp.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -13,7 +15,8 @@ public sealed class FrameFuzzTests
|
||||
{
|
||||
#region Truncated Frame Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_EmptyStream_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -26,7 +29,8 @@ public sealed class FrameFuzzTests
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
@@ -41,7 +45,8 @@ public sealed class FrameFuzzTests
|
||||
.WithMessage("*Incomplete length prefix*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_LengthPrefixOnly_ThrowsException()
|
||||
{
|
||||
// Arrange - Valid length prefix but no payload
|
||||
@@ -57,7 +62,8 @@ public sealed class FrameFuzzTests
|
||||
.WithMessage("*Incomplete payload*");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(50, 10)]
|
||||
[InlineData(100, 25)]
|
||||
[InlineData(1000, 100)]
|
||||
@@ -81,7 +87,8 @@ public sealed class FrameFuzzTests
|
||||
|
||||
#region Invalid Length Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_NegativeLength_ThrowsException()
|
||||
{
|
||||
// Arrange - Negative length (high bit set in signed int)
|
||||
@@ -96,7 +103,8 @@ public sealed class FrameFuzzTests
|
||||
await action.Should().ThrowAsync<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_ZeroLength_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -112,7 +120,8 @@ public sealed class FrameFuzzTests
|
||||
.WithMessage("*Invalid payload length*");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(1)]
|
||||
[InlineData(5)]
|
||||
[InlineData(16)]
|
||||
@@ -132,7 +141,8 @@ public sealed class FrameFuzzTests
|
||||
.WithMessage("*Invalid payload length*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_OversizedLength_ThrowsException()
|
||||
{
|
||||
// Arrange - Frame larger than max allowed
|
||||
@@ -156,7 +166,8 @@ public sealed class FrameFuzzTests
|
||||
|
||||
#region Invalid Frame Type Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(255)]
|
||||
[InlineData(100)]
|
||||
[InlineData(50)]
|
||||
@@ -187,7 +198,8 @@ public sealed class FrameFuzzTests
|
||||
|
||||
#region Corrupted Correlation ID Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_AllZeroCorrelationId_ReadSuccessfully()
|
||||
{
|
||||
// Arrange
|
||||
@@ -210,7 +222,8 @@ public sealed class FrameFuzzTests
|
||||
result!.CorrelationId.Should().Be("00000000000000000000000000000000");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_NonGuidCorrelationBytes_ReadAsHex()
|
||||
{
|
||||
// Arrange - Non-standard bytes that aren't a valid GUID
|
||||
@@ -238,7 +251,8 @@ public sealed class FrameFuzzTests
|
||||
|
||||
#region Random Data Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_RandomBytes_HandledGracefully()
|
||||
{
|
||||
// Arrange
|
||||
@@ -260,7 +274,8 @@ public sealed class FrameFuzzTests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(10)]
|
||||
[InlineData(50)]
|
||||
[InlineData(100)]
|
||||
@@ -294,7 +309,8 @@ public sealed class FrameFuzzTests
|
||||
|
||||
#region Boundary Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_ExactMinimumValidFrame_ParsesSuccessfully()
|
||||
{
|
||||
// Arrange - Minimum valid frame: type (1) + correlation (16) + 0 payload = 17 bytes
|
||||
@@ -317,7 +333,8 @@ public sealed class FrameFuzzTests
|
||||
result.Payload.Length.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_MaxIntLength_RejectedByMaxFrameSize()
|
||||
{
|
||||
// Arrange - Length = Int32.MaxValue
|
||||
@@ -333,7 +350,8 @@ public sealed class FrameFuzzTests
|
||||
.WithMessage("*exceeds maximum*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_ExactMaxFrameSize_Accepted()
|
||||
{
|
||||
// Arrange
|
||||
@@ -358,7 +376,8 @@ public sealed class FrameFuzzTests
|
||||
result.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_OneBytOverMaxFrameSize_Rejected()
|
||||
{
|
||||
// Arrange
|
||||
@@ -386,7 +405,8 @@ public sealed class FrameFuzzTests
|
||||
|
||||
#region Multiple Frames Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_GarbageBetweenFrames_CorruptsSubsequent()
|
||||
{
|
||||
// Arrange - Valid frame, then garbage, then valid frame
|
||||
@@ -424,7 +444,8 @@ public sealed class FrameFuzzTests
|
||||
await action.Should().ThrowAsync<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_MultipleValidFrames_AllParsed()
|
||||
{
|
||||
// Arrange
|
||||
@@ -463,7 +484,8 @@ public sealed class FrameFuzzTests
|
||||
|
||||
#region Payload Content Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_AllByteValues_InPayload_Preserved()
|
||||
{
|
||||
// Arrange - All possible byte values (0-255)
|
||||
@@ -487,7 +509,8 @@ public sealed class FrameFuzzTests
|
||||
result!.Payload.ToArray().Should().BeEquivalentTo(allBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Fuzz_NullBytes_InPayload_Preserved()
|
||||
{
|
||||
// Arrange - Payload with null bytes
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Transport.Tls\StellaOps.Router.Transport.Tls.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Transport.InMemory\StellaOps.Router.Transport.InMemory.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Common\StellaOps.Router.Common.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
|
||||
@@ -3,6 +3,8 @@ using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Frames;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Transport.Tcp.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -13,7 +15,8 @@ public sealed class TcpTransportComplianceTests
|
||||
{
|
||||
#region Protocol Roundtrip Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ProtocolRoundtrip_RequestFrame_AllFieldsPreserved()
|
||||
{
|
||||
// Arrange
|
||||
@@ -57,7 +60,8 @@ public sealed class TcpTransportComplianceTests
|
||||
restored.SupportsStreaming.Should().Be(request.SupportsStreaming);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ProtocolRoundtrip_ResponseFrame_AllFieldsPreserved()
|
||||
{
|
||||
// Arrange
|
||||
@@ -93,7 +97,8 @@ public sealed class TcpTransportComplianceTests
|
||||
restored.HasMoreChunks.Should().Be(response.HasMoreChunks);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ProtocolRoundtrip_BinaryPayload_PreservesAllBytes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -118,7 +123,8 @@ public sealed class TcpTransportComplianceTests
|
||||
readFrame!.Payload.ToArray().Should().BeEquivalentTo(binaryPayload);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(100)]
|
||||
@@ -157,7 +163,8 @@ public sealed class TcpTransportComplianceTests
|
||||
|
||||
#region Frame Type Discrimination Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(FrameType.Request)]
|
||||
[InlineData(FrameType.Response)]
|
||||
[InlineData(FrameType.Hello)]
|
||||
@@ -188,7 +195,8 @@ public sealed class TcpTransportComplianceTests
|
||||
|
||||
#region Message Ordering Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Ordering_MultipleFrames_FifoPreserved()
|
||||
{
|
||||
// Arrange
|
||||
@@ -226,7 +234,8 @@ public sealed class TcpTransportComplianceTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Ordering_MixedFrameTypes_OrderPreserved()
|
||||
{
|
||||
// Arrange
|
||||
@@ -266,7 +275,8 @@ public sealed class TcpTransportComplianceTests
|
||||
|
||||
#region Framing Integrity Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FramingIntegrity_CorrelationIdPreserved()
|
||||
{
|
||||
// Arrange
|
||||
@@ -300,7 +310,8 @@ public sealed class TcpTransportComplianceTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FramingIntegrity_LargeFrame_TransfersCompletely()
|
||||
{
|
||||
// Arrange - 1MB frame
|
||||
@@ -328,7 +339,8 @@ public sealed class TcpTransportComplianceTests
|
||||
|
||||
#region Connection Behavior Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConnectionBehavior_PendingRequestTracker_TracksCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -354,7 +366,8 @@ public sealed class TcpTransportComplianceTests
|
||||
tracker.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConnectionBehavior_RequestTimeout_CancelsCleanly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -370,7 +383,8 @@ public sealed class TcpTransportComplianceTests
|
||||
tracker.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionBehavior_CancelAll_ClearsAllPending()
|
||||
{
|
||||
// Arrange
|
||||
@@ -389,7 +403,8 @@ public sealed class TcpTransportComplianceTests
|
||||
tasks.Should().AllSatisfy(t => t.IsCanceled.Should().BeTrue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ConnectionBehavior_FailRequest_PropagatesToAwaiter()
|
||||
{
|
||||
// Arrange
|
||||
@@ -410,7 +425,8 @@ public sealed class TcpTransportComplianceTests
|
||||
|
||||
#region Determinism Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Determinism_SameInput_SameOutput()
|
||||
{
|
||||
// Run same test multiple times - should always produce same results
|
||||
@@ -445,7 +461,8 @@ public sealed class TcpTransportComplianceTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Determinism_ByteSequence_Consistent()
|
||||
{
|
||||
// Arrange - Write same frame twice
|
||||
@@ -471,7 +488,8 @@ public sealed class TcpTransportComplianceTests
|
||||
|
||||
#region Error Handling Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ErrorHandling_OversizedFrame_Rejected()
|
||||
{
|
||||
// Arrange
|
||||
@@ -495,7 +513,8 @@ public sealed class TcpTransportComplianceTests
|
||||
.WithMessage("*exceeds maximum*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ErrorHandling_EmptyStream_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -508,7 +527,8 @@ public sealed class TcpTransportComplianceTests
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ErrorHandling_CancellationDuringWrite_Throws()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -7,13 +7,16 @@ using StellaOps.Router.Common.Models;
|
||||
using StellaOps.Router.Transport.Tcp;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Router.Transport.Tcp.Tests;
|
||||
|
||||
#region TcpTransportOptions Tests
|
||||
|
||||
public class TcpTransportOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DefaultOptions_HaveCorrectValues()
|
||||
{
|
||||
// Act
|
||||
@@ -30,7 +33,8 @@ public class TcpTransportOptionsTests
|
||||
options.MaxFrameSize.Should().Be(16 * 1024 * 1024);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Host_CanBeSet()
|
||||
{
|
||||
// Act
|
||||
@@ -40,7 +44,8 @@ public class TcpTransportOptionsTests
|
||||
options.Host.Should().Be("192.168.1.100");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Port_CanBeSet()
|
||||
{
|
||||
// Act
|
||||
@@ -50,7 +55,8 @@ public class TcpTransportOptionsTests
|
||||
options.Port.Should().Be(9999);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(1024)]
|
||||
[InlineData(128 * 1024)]
|
||||
[InlineData(1024 * 1024)]
|
||||
@@ -63,7 +69,8 @@ public class TcpTransportOptionsTests
|
||||
options.ReceiveBufferSize.Should().Be(bufferSize);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(1024)]
|
||||
[InlineData(128 * 1024)]
|
||||
[InlineData(1024 * 1024)]
|
||||
@@ -76,7 +83,8 @@ public class TcpTransportOptionsTests
|
||||
options.SendBufferSize.Should().Be(bufferSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MaxReconnectAttempts_CanBeSetToZero()
|
||||
{
|
||||
// Act
|
||||
@@ -93,7 +101,8 @@ public class TcpTransportOptionsTests
|
||||
|
||||
public class FrameProtocolTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteAndReadFrame_RoundTrip()
|
||||
{
|
||||
// Arrange
|
||||
@@ -119,7 +128,8 @@ public class FrameProtocolTests
|
||||
readFrame.Payload.ToArray().Should().Equal(originalFrame.Payload.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteAndReadFrame_EmptyPayload()
|
||||
{
|
||||
// Arrange
|
||||
@@ -142,7 +152,8 @@ public class FrameProtocolTests
|
||||
readFrame.Payload.ToArray().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReadFrame_ReturnsNullOnEmptyStream()
|
||||
{
|
||||
// Arrange
|
||||
@@ -155,7 +166,8 @@ public class FrameProtocolTests
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReadFrame_ThrowsOnOversizedFrame()
|
||||
{
|
||||
// Arrange
|
||||
@@ -176,7 +188,8 @@ public class FrameProtocolTests
|
||||
.WithMessage("*exceeds maximum*");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(FrameType.Request)]
|
||||
[InlineData(FrameType.Response)]
|
||||
[InlineData(FrameType.Cancel)]
|
||||
@@ -203,7 +216,8 @@ public class FrameProtocolTests
|
||||
readFrame!.Type.Should().Be(frameType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteFrame_WithNullCorrelationId_GeneratesNewGuid()
|
||||
{
|
||||
// Arrange
|
||||
@@ -226,7 +240,8 @@ public class FrameProtocolTests
|
||||
Guid.TryParse(readFrame.CorrelationId, out _).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteFrame_BigEndianLength_CorrectByteOrder()
|
||||
{
|
||||
// Arrange
|
||||
@@ -252,7 +267,8 @@ public class FrameProtocolTests
|
||||
actualLength.Should().Be(expectedLength);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReadFrame_IncompleteLengthPrefix_ThrowsException()
|
||||
{
|
||||
// Arrange - Only 2 bytes instead of 4 for length prefix
|
||||
@@ -264,7 +280,8 @@ public class FrameProtocolTests
|
||||
.WithMessage("*Incomplete length prefix*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReadFrame_InvalidPayloadLength_TooSmall_ThrowsException()
|
||||
{
|
||||
// Arrange - Length of 5 is too small (header is 17 bytes minimum)
|
||||
@@ -280,7 +297,8 @@ public class FrameProtocolTests
|
||||
.WithMessage("*Invalid payload length*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReadFrame_IncompletePayload_ThrowsException()
|
||||
{
|
||||
// Arrange - Claim to have 100 bytes but only provide 10
|
||||
@@ -297,7 +315,8 @@ public class FrameProtocolTests
|
||||
.WithMessage("*Incomplete payload*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReadFrame_WithLargePayload_ReadsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -322,7 +341,8 @@ public class FrameProtocolTests
|
||||
readFrame!.Payload.ToArray().Should().Equal(largePayload);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteFrame_CancellationRequested_ThrowsOperationCanceled()
|
||||
{
|
||||
// Arrange
|
||||
@@ -348,7 +368,8 @@ public class FrameProtocolTests
|
||||
|
||||
public class PendingRequestTrackerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TrackRequest_CompletesWithResponse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -371,7 +392,8 @@ public class PendingRequestTrackerTests
|
||||
response.Type.Should().Be(expectedResponse.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TrackRequest_CancelsOnTokenCancellation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -388,7 +410,8 @@ public class PendingRequestTrackerTests
|
||||
await action.Should().ThrowAsync<TaskCanceledException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Count_ReturnsCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -401,7 +424,8 @@ public class PendingRequestTrackerTests
|
||||
tracker.Count.Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CancelAll_CancelsAllPendingRequests()
|
||||
{
|
||||
// Arrange
|
||||
@@ -417,7 +441,8 @@ public class PendingRequestTrackerTests
|
||||
task2.IsCanceled.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FailRequest_SetsException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -433,7 +458,8 @@ public class PendingRequestTrackerTests
|
||||
task.Exception?.InnerException.Should().BeOfType<InvalidOperationException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CancelRequest_CancelsSpecificRequest()
|
||||
{
|
||||
// Arrange
|
||||
@@ -452,7 +478,8 @@ public class PendingRequestTrackerTests
|
||||
task2.IsCompleted.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CompleteRequest_WithUnknownId_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
@@ -467,7 +494,8 @@ public class PendingRequestTrackerTests
|
||||
action.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CancelRequest_WithUnknownId_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
@@ -481,7 +509,8 @@ public class PendingRequestTrackerTests
|
||||
action.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FailRequest_WithUnknownId_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
@@ -495,7 +524,8 @@ public class PendingRequestTrackerTests
|
||||
action.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispose_CancelsAllPendingRequests()
|
||||
{
|
||||
// Arrange
|
||||
@@ -509,7 +539,8 @@ public class PendingRequestTrackerTests
|
||||
(task.IsCanceled || task.IsFaulted).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Dispose_CanBeCalledMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -527,7 +558,8 @@ public class PendingRequestTrackerTests
|
||||
action.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CompleteRequest_DecreasesCount()
|
||||
{
|
||||
// Arrange
|
||||
@@ -553,7 +585,8 @@ public class PendingRequestTrackerTests
|
||||
|
||||
public class TcpTransportServerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StartAsync_StartsListening()
|
||||
{
|
||||
// Arrange
|
||||
@@ -568,7 +601,8 @@ public class TcpTransportServerTests
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StopAsync_CanBeCalledWithoutStart()
|
||||
{
|
||||
// Arrange
|
||||
@@ -582,7 +616,8 @@ public class TcpTransportServerTests
|
||||
await action.Should().NotThrowAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConnectionCount_InitiallyZero()
|
||||
{
|
||||
// Arrange
|
||||
@@ -593,7 +628,8 @@ public class TcpTransportServerTests
|
||||
server.ConnectionCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DisposeAsync_CanBeCalledMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -612,7 +648,8 @@ public class TcpTransportServerTests
|
||||
await action.Should().NotThrowAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StartAsync_TwiceDoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
@@ -643,7 +680,8 @@ public class TcpTransportClientTests
|
||||
NullLogger<TcpTransportClient>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Constructor_InitializesCorrectly()
|
||||
{
|
||||
// Act
|
||||
@@ -653,7 +691,8 @@ public class TcpTransportClientTests
|
||||
client.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConnectAsync_WithoutHost_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -675,7 +714,8 @@ public class TcpTransportClientTests
|
||||
.WithMessage("*Host is not configured*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConnectAsync_WithEmptyHost_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -697,7 +737,8 @@ public class TcpTransportClientTests
|
||||
.WithMessage("*Host is not configured*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DisposeAsync_CanBeCalledMultipleTimes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -715,7 +756,8 @@ public class TcpTransportClientTests
|
||||
await action.Should().NotThrowAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DisconnectAsync_WithoutConnect_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
@@ -728,7 +770,8 @@ public class TcpTransportClientTests
|
||||
await action.Should().NotThrowAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CancelAllInflight_WithNoInflight_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
Reference in New Issue
Block a user