Add Canonical JSON serialization library with tests and documentation
- Implemented CanonJson class for deterministic JSON serialization and hashing. - Added unit tests for CanonJson functionality, covering various scenarios including key sorting, handling of nested objects, arrays, and special characters. - Created project files for the Canonical JSON library and its tests, including necessary package references. - Added README.md for library usage and API reference. - Introduced RabbitMqIntegrationFactAttribute for conditional RabbitMQ integration tests.
This commit is contained in:
@@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Router.Testing.Fixtures;
|
||||
using Testcontainers.RabbitMq;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace StellaOps.Router.Transport.RabbitMq.Tests.Fixtures;
|
||||
|
||||
@@ -82,15 +83,37 @@ public sealed class RabbitMqContainerFixture : RouterCollectionFixture, IAsyncDi
|
||||
/// <inheritdoc />
|
||||
public override async Task InitializeAsync()
|
||||
{
|
||||
_container = new RabbitMqBuilder()
|
||||
.WithImage("rabbitmq:3.12-management")
|
||||
.WithPortBinding(5672, true)
|
||||
.WithPortBinding(15672, true)
|
||||
.WithUsername("guest")
|
||||
.WithPassword("guest")
|
||||
.Build();
|
||||
try
|
||||
{
|
||||
_container = new RabbitMqBuilder()
|
||||
.WithImage("rabbitmq:3.12-management")
|
||||
.WithPortBinding(5672, true)
|
||||
.WithPortBinding(15672, true)
|
||||
.WithUsername("guest")
|
||||
.WithPassword("guest")
|
||||
.Build();
|
||||
|
||||
await _container.StartAsync();
|
||||
await _container.StartAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_container is not null)
|
||||
{
|
||||
await _container.DisposeAsync();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore cleanup failures during skip.
|
||||
}
|
||||
|
||||
_container = null;
|
||||
|
||||
throw SkipException.ForSkip(
|
||||
$"RabbitMQ integration tests require Docker/Testcontainers. Skipping because the container failed to start: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Router.Transport.RabbitMq.Tests.Fixtures;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class RabbitMqIntegrationFactAttribute : FactAttribute
|
||||
{
|
||||
public RabbitMqIntegrationFactAttribute()
|
||||
{
|
||||
var enabled = Environment.GetEnvironmentVariable("STELLAOPS_TEST_RABBITMQ");
|
||||
if (!string.Equals(enabled, "1", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(enabled, "true", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Skip = "RabbitMQ integration tests are opt-in. Set STELLAOPS_TEST_RABBITMQ=1 (requires Docker/Testcontainers).";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public sealed class RabbitMqIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Connection Tests
|
||||
|
||||
[Fact]
|
||||
[RabbitMqIntegrationFact]
|
||||
public async Task ServerStartAsync_WithRealBroker_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
@@ -74,7 +74,7 @@ public sealed class RabbitMqIntegrationTests : IAsyncLifetime
|
||||
_server.ConnectionCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[RabbitMqIntegrationFact]
|
||||
public async Task ServerStopAsync_AfterStart_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
@@ -88,7 +88,7 @@ public sealed class RabbitMqIntegrationTests : IAsyncLifetime
|
||||
await act.Should().NotThrowAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[RabbitMqIntegrationFact]
|
||||
public async Task ClientConnectAsync_WithRealBroker_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
@@ -108,7 +108,7 @@ public sealed class RabbitMqIntegrationTests : IAsyncLifetime
|
||||
await act.Should().NotThrowAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[RabbitMqIntegrationFact]
|
||||
public async Task ClientDisconnectAsync_AfterConnect_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
@@ -133,7 +133,7 @@ public sealed class RabbitMqIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Hello Frame Tests
|
||||
|
||||
[Fact]
|
||||
[RabbitMqIntegrationFact]
|
||||
public async Task ClientConnectAsync_SendsHelloFrame_ServerReceives()
|
||||
{
|
||||
// Arrange
|
||||
@@ -180,7 +180,7 @@ public sealed class RabbitMqIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Heartbeat Tests
|
||||
|
||||
[Fact]
|
||||
[RabbitMqIntegrationFact]
|
||||
public async Task ClientSendHeartbeatAsync_RealBroker_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
@@ -210,7 +210,7 @@ public sealed class RabbitMqIntegrationTests : IAsyncLifetime
|
||||
await act.Should().NotThrowAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[RabbitMqIntegrationFact]
|
||||
public async Task ServerReceivesHeartbeat_UpdatesLastHeartbeatUtc()
|
||||
{
|
||||
// Arrange
|
||||
@@ -272,7 +272,7 @@ public sealed class RabbitMqIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Queue Declaration Tests
|
||||
|
||||
[Fact]
|
||||
[RabbitMqIntegrationFact]
|
||||
public async Task ServerStartAsync_CreatesExchangesAndQueues()
|
||||
{
|
||||
// Arrange
|
||||
@@ -286,7 +286,7 @@ public sealed class RabbitMqIntegrationTests : IAsyncLifetime
|
||||
// but the lack of exception indicates success
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[RabbitMqIntegrationFact]
|
||||
public async Task ClientConnectAsync_CreatesResponseQueue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -309,7 +309,7 @@ public sealed class RabbitMqIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Auto-Delete Queue Tests
|
||||
|
||||
[Fact]
|
||||
[RabbitMqIntegrationFact]
|
||||
public async Task AutoDeleteQueues_AreCleanedUpOnDisconnect()
|
||||
{
|
||||
// Arrange
|
||||
@@ -343,7 +343,7 @@ public sealed class RabbitMqIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Prefetch Tests
|
||||
|
||||
[Fact]
|
||||
[RabbitMqIntegrationFact]
|
||||
public async Task PrefetchCount_IsAppliedOnConnect()
|
||||
{
|
||||
// Arrange
|
||||
@@ -372,7 +372,7 @@ public sealed class RabbitMqIntegrationTests : IAsyncLifetime
|
||||
|
||||
#region Multiple Connections Tests
|
||||
|
||||
[Fact]
|
||||
[RabbitMqIntegrationFact]
|
||||
public async Task MultipleClients_CanConnectSimultaneously()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -112,10 +112,10 @@ public sealed class RabbitMqTransportClientTests
|
||||
#region CancelAllInflight Tests
|
||||
|
||||
[Fact]
|
||||
public void CancelAllInflight_WhenNoInflightRequests_DoesNotThrow()
|
||||
public async Task CancelAllInflight_WhenNoInflightRequests_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
using var client = CreateClient();
|
||||
await using var client = CreateClient();
|
||||
|
||||
// Act & Assert - should not throw
|
||||
client.CancelAllInflight("TestReason");
|
||||
@@ -373,12 +373,12 @@ public sealed class RabbitMqTransportClientConfigurationTests
|
||||
// Arrange
|
||||
var options = new RabbitMqTransportOptions
|
||||
{
|
||||
QueuePrefix = "myapp"
|
||||
ExchangePrefix = "myapp"
|
||||
};
|
||||
|
||||
// Assert
|
||||
options.RequestExchange.Should().Be("myapp.request");
|
||||
options.ResponseExchange.Should().Be("myapp.response");
|
||||
options.RequestExchange.Should().Be("myapp.requests");
|
||||
options.ResponseExchange.Should().Be("myapp.responses");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -388,7 +388,7 @@ public sealed class RabbitMqTransportClientConfigurationTests
|
||||
var options = new RabbitMqTransportOptions();
|
||||
|
||||
// Assert
|
||||
options.RequestExchange.Should().Be("stellaops.request");
|
||||
options.ResponseExchange.Should().Be("stellaops.response");
|
||||
options.RequestExchange.Should().Be("stella.router.requests");
|
||||
options.ResponseExchange.Should().Be("stella.router.responses");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,10 +99,10 @@ public sealed class RabbitMqTransportServerTests
|
||||
#region Connection Management Tests
|
||||
|
||||
[Fact]
|
||||
public void GetConnectionState_WithUnknownConnectionId_ReturnsNull()
|
||||
public async Task GetConnectionState_WithUnknownConnectionId_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
using var server = CreateServer();
|
||||
await using var server = CreateServer();
|
||||
|
||||
// Act
|
||||
var result = server.GetConnectionState("unknown-connection");
|
||||
@@ -112,10 +112,10 @@ public sealed class RabbitMqTransportServerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConnections_WhenEmpty_ReturnsEmptyEnumerable()
|
||||
public async Task GetConnections_WhenEmpty_ReturnsEmptyEnumerable()
|
||||
{
|
||||
// Arrange
|
||||
using var server = CreateServer();
|
||||
await using var server = CreateServer();
|
||||
|
||||
// Act
|
||||
var result = server.GetConnections().ToList();
|
||||
@@ -125,10 +125,10 @@ public sealed class RabbitMqTransportServerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectionCount_WhenEmpty_ReturnsZero()
|
||||
public async Task ConnectionCount_WhenEmpty_ReturnsZero()
|
||||
{
|
||||
// Arrange
|
||||
using var server = CreateServer();
|
||||
await using var server = CreateServer();
|
||||
|
||||
// Act
|
||||
var result = server.ConnectionCount;
|
||||
@@ -138,10 +138,10 @@ public sealed class RabbitMqTransportServerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveConnection_WithUnknownConnectionId_DoesNotThrow()
|
||||
public async Task RemoveConnection_WithUnknownConnectionId_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
using var server = CreateServer();
|
||||
await using var server = CreateServer();
|
||||
|
||||
// Act
|
||||
var act = () => server.RemoveConnection("unknown-connection");
|
||||
@@ -155,10 +155,10 @@ public sealed class RabbitMqTransportServerTests
|
||||
#region Event Handler Tests
|
||||
|
||||
[Fact]
|
||||
public void OnConnection_CanBeRegistered()
|
||||
public async Task OnConnection_CanBeRegistered()
|
||||
{
|
||||
// Arrange
|
||||
using var server = CreateServer();
|
||||
await using var server = CreateServer();
|
||||
var connectionReceived = false;
|
||||
|
||||
// Act
|
||||
@@ -172,10 +172,10 @@ public sealed class RabbitMqTransportServerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnDisconnection_CanBeRegistered()
|
||||
public async Task OnDisconnection_CanBeRegistered()
|
||||
{
|
||||
// Arrange
|
||||
using var server = CreateServer();
|
||||
await using var server = CreateServer();
|
||||
var disconnectionReceived = false;
|
||||
|
||||
// Act
|
||||
@@ -189,10 +189,10 @@ public sealed class RabbitMqTransportServerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnFrame_CanBeRegistered()
|
||||
public async Task OnFrame_CanBeRegistered()
|
||||
{
|
||||
// Arrange
|
||||
using var server = CreateServer();
|
||||
await using var server = CreateServer();
|
||||
var frameReceived = false;
|
||||
|
||||
// Act
|
||||
@@ -252,7 +252,7 @@ public sealed class RabbitMqTransportServerTests
|
||||
public async Task SendFrameAsync_WithUnknownConnection_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
using var server = CreateServer();
|
||||
await using var server = CreateServer();
|
||||
|
||||
var frame = new Frame
|
||||
{
|
||||
@@ -277,7 +277,7 @@ public sealed class RabbitMqTransportServerTests
|
||||
public async Task StopAsync_WhenNotStarted_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
using var server = CreateServer();
|
||||
await using var server = CreateServer();
|
||||
|
||||
// Act
|
||||
var act = async () => await server.StopAsync(CancellationToken.None);
|
||||
|
||||
Reference in New Issue
Block a user