580 lines
15 KiB
C#
580 lines
15 KiB
C#
using System.Text;
|
|
using StellaOps.Router.Common.Enums;
|
|
using StellaOps.Router.Common.Frames;
|
|
using StellaOps.Router.Common.Models;
|
|
|
|
using StellaOps.TestKit;
|
|
namespace StellaOps.Router.Common.Tests;
|
|
|
|
/// <summary>
|
|
/// Unit tests for <see cref="FrameConverter"/>.
|
|
/// </summary>
|
|
public sealed class FrameConverterTests
|
|
{
|
|
#region ToFrame (RequestFrame) Tests
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToFrame_RequestFrame_ReturnsFrameWithRequestType()
|
|
{
|
|
// Arrange
|
|
var request = CreateTestRequestFrame();
|
|
|
|
// Act
|
|
var frame = FrameConverter.ToFrame(request);
|
|
|
|
// Assert
|
|
frame.Type.Should().Be(FrameType.Request);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToFrame_RequestFrame_SetsCorrelationIdFromRequest()
|
|
{
|
|
// Arrange
|
|
var request = CreateTestRequestFrame(correlationId: "test-correlation-123");
|
|
|
|
// Act
|
|
var frame = FrameConverter.ToFrame(request);
|
|
|
|
// Assert
|
|
frame.CorrelationId.Should().Be("test-correlation-123");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToFrame_RequestFrame_UsesRequestIdWhenCorrelationIdIsNull()
|
|
{
|
|
// Arrange
|
|
var request = new RequestFrame
|
|
{
|
|
RequestId = "request-id-456",
|
|
CorrelationId = null,
|
|
Method = "GET",
|
|
Path = "/test"
|
|
};
|
|
|
|
// Act
|
|
var frame = FrameConverter.ToFrame(request);
|
|
|
|
// Assert
|
|
frame.CorrelationId.Should().Be("request-id-456");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToFrame_RequestFrame_SerializesPayload()
|
|
{
|
|
// Arrange
|
|
var request = CreateTestRequestFrame();
|
|
|
|
// Act
|
|
var frame = FrameConverter.ToFrame(request);
|
|
|
|
// Assert
|
|
frame.Payload.Length.Should().BeGreaterThan(0);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToRequestFrame Tests
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToRequestFrame_ValidRequestFrame_ReturnsRequestFrame()
|
|
{
|
|
// Arrange
|
|
var originalRequest = CreateTestRequestFrame();
|
|
var frame = FrameConverter.ToFrame(originalRequest);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToRequestFrame_WrongFrameType_ReturnsNull()
|
|
{
|
|
// Arrange
|
|
var frame = new Frame
|
|
{
|
|
Type = FrameType.Response,
|
|
CorrelationId = "test",
|
|
Payload = Array.Empty<byte>()
|
|
};
|
|
|
|
// Act
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToRequestFrame_InvalidJson_ReturnsNull()
|
|
{
|
|
// Arrange
|
|
var frame = new Frame
|
|
{
|
|
Type = FrameType.Request,
|
|
CorrelationId = "test",
|
|
Payload = Encoding.UTF8.GetBytes("invalid json {{{")
|
|
};
|
|
|
|
// Act
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToRequestFrame_RoundTrip_PreservesRequestId()
|
|
{
|
|
// Arrange
|
|
var originalRequest = CreateTestRequestFrame(requestId: "unique-request-id");
|
|
var frame = FrameConverter.ToFrame(originalRequest);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result!.RequestId.Should().Be("unique-request-id");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToRequestFrame_RoundTrip_PreservesMethod()
|
|
{
|
|
// Arrange
|
|
var originalRequest = CreateTestRequestFrame(method: "DELETE");
|
|
var frame = FrameConverter.ToFrame(originalRequest);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result!.Method.Should().Be("DELETE");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToRequestFrame_RoundTrip_PreservesPath()
|
|
{
|
|
// Arrange
|
|
var originalRequest = CreateTestRequestFrame(path: "/api/users/123");
|
|
var frame = FrameConverter.ToFrame(originalRequest);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result!.Path.Should().Be("/api/users/123");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToRequestFrame_RoundTrip_PreservesHeaders()
|
|
{
|
|
// Arrange
|
|
var headers = new Dictionary<string, string>
|
|
{
|
|
["Content-Type"] = "application/json",
|
|
["X-Custom-Header"] = "custom-value"
|
|
};
|
|
var originalRequest = new RequestFrame
|
|
{
|
|
RequestId = "test-id",
|
|
Method = "POST",
|
|
Path = "/test",
|
|
Headers = headers
|
|
};
|
|
var frame = FrameConverter.ToFrame(originalRequest);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result!.Headers.Should().ContainKey("Content-Type");
|
|
result.Headers["Content-Type"].Should().Be("application/json");
|
|
result.Headers["X-Custom-Header"].Should().Be("custom-value");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToRequestFrame_RoundTrip_PreservesPayload()
|
|
{
|
|
// Arrange
|
|
var payloadBytes = Encoding.UTF8.GetBytes("{\"key\":\"value\"}");
|
|
var originalRequest = new RequestFrame
|
|
{
|
|
RequestId = "test-id",
|
|
Method = "POST",
|
|
Path = "/test",
|
|
Payload = payloadBytes
|
|
};
|
|
var frame = FrameConverter.ToFrame(originalRequest);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result!.Payload.ToArray().Should().BeEquivalentTo(payloadBytes);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToRequestFrame_RoundTrip_PreservesTimeoutSeconds()
|
|
{
|
|
// Arrange
|
|
var originalRequest = new RequestFrame
|
|
{
|
|
RequestId = "test-id",
|
|
Method = "GET",
|
|
Path = "/test",
|
|
TimeoutSeconds = 60
|
|
};
|
|
var frame = FrameConverter.ToFrame(originalRequest);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result!.TimeoutSeconds.Should().Be(60);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToRequestFrame_RoundTrip_PreservesSupportsStreaming()
|
|
{
|
|
// Arrange
|
|
var originalRequest = new RequestFrame
|
|
{
|
|
RequestId = "test-id",
|
|
Method = "GET",
|
|
Path = "/test",
|
|
SupportsStreaming = true
|
|
};
|
|
var frame = FrameConverter.ToFrame(originalRequest);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result!.SupportsStreaming.Should().BeTrue();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToFrame (ResponseFrame) Tests
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToFrame_ResponseFrame_ReturnsFrameWithResponseType()
|
|
{
|
|
// Arrange
|
|
var response = CreateTestResponseFrame();
|
|
|
|
// Act
|
|
var frame = FrameConverter.ToFrame(response);
|
|
|
|
// Assert
|
|
frame.Type.Should().Be(FrameType.Response);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToFrame_ResponseFrame_SetsCorrelationIdToRequestId()
|
|
{
|
|
// Arrange
|
|
var response = CreateTestResponseFrame(requestId: "req-123");
|
|
|
|
// Act
|
|
var frame = FrameConverter.ToFrame(response);
|
|
|
|
// Assert
|
|
frame.CorrelationId.Should().Be("req-123");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToResponseFrame Tests
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToResponseFrame_ValidResponseFrame_ReturnsResponseFrame()
|
|
{
|
|
// Arrange
|
|
var originalResponse = CreateTestResponseFrame();
|
|
var frame = FrameConverter.ToFrame(originalResponse);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToResponseFrame(frame);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToResponseFrame_WrongFrameType_ReturnsNull()
|
|
{
|
|
// Arrange
|
|
var frame = new Frame
|
|
{
|
|
Type = FrameType.Request,
|
|
CorrelationId = "test",
|
|
Payload = Array.Empty<byte>()
|
|
};
|
|
|
|
// Act
|
|
var result = FrameConverter.ToResponseFrame(frame);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToResponseFrame_InvalidJson_ReturnsNull()
|
|
{
|
|
// Arrange
|
|
var frame = new Frame
|
|
{
|
|
Type = FrameType.Response,
|
|
CorrelationId = "test",
|
|
Payload = Encoding.UTF8.GetBytes("not valid json")
|
|
};
|
|
|
|
// Act
|
|
var result = FrameConverter.ToResponseFrame(frame);
|
|
|
|
// Assert
|
|
result.Should().BeNull();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToResponseFrame_RoundTrip_PreservesRequestId()
|
|
{
|
|
// Arrange
|
|
var originalResponse = CreateTestResponseFrame(requestId: "original-req-id");
|
|
var frame = FrameConverter.ToFrame(originalResponse);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToResponseFrame(frame);
|
|
|
|
// Assert
|
|
result!.RequestId.Should().Be("original-req-id");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToResponseFrame_RoundTrip_PreservesStatusCode()
|
|
{
|
|
// Arrange
|
|
var originalResponse = CreateTestResponseFrame(statusCode: 404);
|
|
var frame = FrameConverter.ToFrame(originalResponse);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToResponseFrame(frame);
|
|
|
|
// Assert
|
|
result!.StatusCode.Should().Be(404);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToResponseFrame_RoundTrip_PreservesHeaders()
|
|
{
|
|
// Arrange
|
|
var headers = new Dictionary<string, string>
|
|
{
|
|
["Content-Type"] = "application/json",
|
|
["Cache-Control"] = "no-cache"
|
|
};
|
|
var originalResponse = new ResponseFrame
|
|
{
|
|
RequestId = "test-id",
|
|
StatusCode = 200,
|
|
Headers = headers
|
|
};
|
|
var frame = FrameConverter.ToFrame(originalResponse);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToResponseFrame(frame);
|
|
|
|
// Assert
|
|
result!.Headers["Content-Type"].Should().Be("application/json");
|
|
result.Headers["Cache-Control"].Should().Be("no-cache");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToResponseFrame_RoundTrip_PreservesPayload()
|
|
{
|
|
// Arrange
|
|
var payloadBytes = Encoding.UTF8.GetBytes("{\"result\":\"success\"}");
|
|
var originalResponse = new ResponseFrame
|
|
{
|
|
RequestId = "test-id",
|
|
StatusCode = 200,
|
|
Payload = payloadBytes
|
|
};
|
|
var frame = FrameConverter.ToFrame(originalResponse);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToResponseFrame(frame);
|
|
|
|
// Assert
|
|
result!.Payload.ToArray().Should().BeEquivalentTo(payloadBytes);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToResponseFrame_RoundTrip_PreservesHasMoreChunks()
|
|
{
|
|
// Arrange
|
|
var originalResponse = new ResponseFrame
|
|
{
|
|
RequestId = "test-id",
|
|
StatusCode = 200,
|
|
HasMoreChunks = true
|
|
};
|
|
var frame = FrameConverter.ToFrame(originalResponse);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToResponseFrame(frame);
|
|
|
|
// Assert
|
|
result!.HasMoreChunks.Should().BeTrue();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Edge Cases
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToRequestFrame_EmptyPayload_ReturnsEmptyPayload()
|
|
{
|
|
// Arrange
|
|
var originalRequest = new RequestFrame
|
|
{
|
|
RequestId = "test-id",
|
|
Method = "GET",
|
|
Path = "/test",
|
|
Payload = Array.Empty<byte>()
|
|
};
|
|
var frame = FrameConverter.ToFrame(originalRequest);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result!.Payload.IsEmpty.Should().BeTrue();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToRequestFrame_NullHeaders_ReturnsEmptyHeaders()
|
|
{
|
|
// Arrange
|
|
var originalRequest = new RequestFrame
|
|
{
|
|
RequestId = "test-id",
|
|
Method = "GET",
|
|
Path = "/test"
|
|
};
|
|
var frame = FrameConverter.ToFrame(originalRequest);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result!.Headers.Should().NotBeNull();
|
|
result.Headers.Should().BeEmpty();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToResponseFrame_EmptyPayload_ReturnsEmptyPayload()
|
|
{
|
|
// Arrange
|
|
var originalResponse = new ResponseFrame
|
|
{
|
|
RequestId = "test-id",
|
|
StatusCode = 204,
|
|
Payload = Array.Empty<byte>()
|
|
};
|
|
var frame = FrameConverter.ToFrame(originalResponse);
|
|
|
|
// Act
|
|
var result = FrameConverter.ToResponseFrame(frame);
|
|
|
|
// Assert
|
|
result!.Payload.IsEmpty.Should().BeTrue();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ToFrame_LargePayload_Succeeds()
|
|
{
|
|
// Arrange
|
|
var largePayload = new byte[1024 * 1024]; // 1MB
|
|
Random.Shared.NextBytes(largePayload);
|
|
var originalRequest = new RequestFrame
|
|
{
|
|
RequestId = "test-id",
|
|
Method = "POST",
|
|
Path = "/upload",
|
|
Payload = largePayload
|
|
};
|
|
|
|
// Act
|
|
var frame = FrameConverter.ToFrame(originalRequest);
|
|
var result = FrameConverter.ToRequestFrame(frame);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.Payload.ToArray().Should().BeEquivalentTo(largePayload);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
private static RequestFrame CreateTestRequestFrame(
|
|
string? requestId = null,
|
|
string? correlationId = null,
|
|
string method = "GET",
|
|
string path = "/test")
|
|
{
|
|
return new RequestFrame
|
|
{
|
|
RequestId = requestId ?? Guid.NewGuid().ToString("N"),
|
|
CorrelationId = correlationId,
|
|
Method = method,
|
|
Path = path
|
|
};
|
|
}
|
|
|
|
private static ResponseFrame CreateTestResponseFrame(
|
|
string? requestId = null,
|
|
int statusCode = 200)
|
|
{
|
|
return new ResponseFrame
|
|
{
|
|
RequestId = requestId ?? Guid.NewGuid().ToString("N"),
|
|
StatusCode = statusCode
|
|
};
|
|
}
|
|
|
|
#endregion
|
|
}
|