Add unit tests for RabbitMq and Udp transport servers and clients
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:
master
2025-12-05 19:01:12 +02:00
parent 53508ceccb
commit cc69d332e3
245 changed files with 22440 additions and 27719 deletions

View File

@@ -0,0 +1,539 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace StellaOps.Microservice.SourceGen.Tests;
/// <summary>
/// Unit tests for <see cref="StellaEndpointGenerator"/>.
/// </summary>
public sealed class StellaEndpointGeneratorTests
{
#region Helper Methods
private static GeneratorDriverRunResult RunGenerator(string source)
{
var syntaxTree = CSharpSyntaxTree.ParseText(source);
var references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Task).Assembly.Location),
MetadataReference.CreateFromFile(typeof(StellaEndpointAttribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Router.Common.Models.EndpointDescriptor).Assembly.Location),
};
// Add System.Runtime reference
var runtimePath = Path.GetDirectoryName(typeof(object).Assembly.Location)!;
references.Add(MetadataReference.CreateFromFile(Path.Combine(runtimePath, "System.Runtime.dll")));
var compilation = CSharpCompilation.Create(
assemblyName: "TestAssembly",
syntaxTrees: [syntaxTree],
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
var generator = new StellaEndpointGenerator();
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _);
return driver.GetRunResult();
}
private static ImmutableArray<Diagnostic> GetDiagnostics(GeneratorDriverRunResult result)
{
return result.Results.SelectMany(r => r.Diagnostics).ToImmutableArray();
}
private static string? GetGeneratedSource(GeneratorDriverRunResult result, string hintName)
{
var generatedSources = result.Results
.SelectMany(r => r.GeneratedSources)
.Where(s => s.HintName == hintName)
.ToList();
return generatedSources.FirstOrDefault().SourceText?.ToString();
}
#endregion
#region Basic Generation Tests
[Fact]
public void Generator_WithTypedEndpoint_GeneratesSource()
{
// Arrange
var source = """
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
namespace TestNamespace
{
public record GetUserRequest(string UserId);
public record GetUserResponse(string Name, string Email);
[StellaEndpoint("GET", "/users/{userId}")]
public class GetUserEndpoint : IStellaEndpoint<GetUserRequest, GetUserResponse>
{
public Task<GetUserResponse> HandleAsync(GetUserRequest request, CancellationToken cancellationToken)
{
return Task.FromResult(new GetUserResponse("Test", "test@example.com"));
}
}
}
""";
// Act
var result = RunGenerator(source);
// Assert
result.GeneratedTrees.Should().NotBeEmpty();
var generatedSource = GetGeneratedSource(result, "StellaEndpoints.g.cs");
generatedSource.Should().NotBeNullOrEmpty();
generatedSource.Should().Contain("GetUserEndpoint");
generatedSource.Should().Contain("/users/{userId}");
generatedSource.Should().Contain("GET");
}
[Fact]
public void Generator_WithRawEndpoint_GeneratesSource()
{
// Arrange
var source = """
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
namespace TestNamespace
{
[StellaEndpoint("POST", "/raw/upload")]
public class UploadEndpoint : IRawStellaEndpoint
{
public Task<RawResponse> HandleAsync(RawRequestContext context, CancellationToken cancellationToken)
{
return Task.FromResult(RawResponse.Ok());
}
}
}
""";
// Act
var result = RunGenerator(source);
// Assert
result.GeneratedTrees.Should().NotBeEmpty();
var generatedSource = GetGeneratedSource(result, "StellaEndpoints.g.cs");
generatedSource.Should().NotBeNullOrEmpty();
generatedSource.Should().Contain("UploadEndpoint");
generatedSource.Should().Contain("/raw/upload");
}
[Fact]
public void Generator_WithMultipleEndpoints_GeneratesAll()
{
// Arrange
var source = """
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
namespace TestNamespace
{
public record Request1();
public record Response1();
public record Request2();
public record Response2();
[StellaEndpoint("GET", "/endpoint1")]
public class Endpoint1 : IStellaEndpoint<Request1, Response1>
{
public Task<Response1> HandleAsync(Request1 request, CancellationToken cancellationToken)
=> Task.FromResult(new Response1());
}
[StellaEndpoint("POST", "/endpoint2")]
public class Endpoint2 : IStellaEndpoint<Request2, Response2>
{
public Task<Response2> HandleAsync(Request2 request, CancellationToken cancellationToken)
=> Task.FromResult(new Response2());
}
}
""";
// Act
var result = RunGenerator(source);
// Assert
var generatedSource = GetGeneratedSource(result, "StellaEndpoints.g.cs");
generatedSource.Should().NotBeNullOrEmpty();
generatedSource.Should().Contain("Endpoint1");
generatedSource.Should().Contain("Endpoint2");
generatedSource.Should().Contain("/endpoint1");
generatedSource.Should().Contain("/endpoint2");
}
[Fact]
public void Generator_WithNoEndpoints_GeneratesNothing()
{
// Arrange
var source = """
namespace TestNamespace
{
public class RegularClass
{
public void DoSomething() { }
}
}
""";
// Act
var result = RunGenerator(source);
// Assert
var generatedSource = GetGeneratedSource(result, "StellaEndpoints.g.cs");
generatedSource.Should().BeNull();
}
#endregion
#region Attribute Property Tests
[Fact]
public void Generator_WithTimeout_IncludesTimeoutInGeneration()
{
// Arrange
var source = """
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
namespace TestNamespace
{
public record Req();
public record Resp();
[StellaEndpoint("GET", "/slow", TimeoutSeconds = 120)]
public class SlowEndpoint : IStellaEndpoint<Req, Resp>
{
public Task<Resp> HandleAsync(Req request, CancellationToken cancellationToken)
=> Task.FromResult(new Resp());
}
}
""";
// Act
var result = RunGenerator(source);
// Assert
var generatedSource = GetGeneratedSource(result, "StellaEndpoints.g.cs");
generatedSource.Should().Contain("FromSeconds(120)");
}
[Fact]
public void Generator_WithStreaming_IncludesStreamingFlag()
{
// Arrange
var source = """
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
namespace TestNamespace
{
[StellaEndpoint("POST", "/stream", SupportsStreaming = true)]
public class StreamEndpoint : IRawStellaEndpoint
{
public Task<RawResponse> HandleAsync(RawRequestContext context, CancellationToken cancellationToken)
=> Task.FromResult(RawResponse.Ok());
}
}
""";
// Act
var result = RunGenerator(source);
// Assert
var generatedSource = GetGeneratedSource(result, "StellaEndpoints.g.cs");
generatedSource.Should().Contain("SupportsStreaming = true");
}
[Fact]
public void Generator_WithRequiredClaims_IncludesClaims()
{
// Arrange
var source = """
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
namespace TestNamespace
{
public record Req();
public record Resp();
[StellaEndpoint("DELETE", "/admin/users", RequiredClaims = new[] { "admin", "user:delete" })]
public class AdminEndpoint : IStellaEndpoint<Req, Resp>
{
public Task<Resp> HandleAsync(Req request, CancellationToken cancellationToken)
=> Task.FromResult(new Resp());
}
}
""";
// Act
var result = RunGenerator(source);
// Assert
var generatedSource = GetGeneratedSource(result, "StellaEndpoints.g.cs");
generatedSource.Should().Contain("admin");
generatedSource.Should().Contain("user:delete");
generatedSource.Should().Contain("ClaimRequirement");
}
#endregion
#region HTTP Method Tests
[Theory]
[InlineData("GET")]
[InlineData("POST")]
[InlineData("PUT")]
[InlineData("DELETE")]
[InlineData("PATCH")]
public void Generator_WithHttpMethod_NormalizesToUppercase(string method)
{
// Arrange
var source = $$"""
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
namespace TestNamespace
{
public record Req();
public record Resp();
[StellaEndpoint("{{method.ToLowerInvariant()}}", "/test")]
public class TestEndpoint : IStellaEndpoint<Req, Resp>
{
public Task<Resp> HandleAsync(Req request, CancellationToken cancellationToken)
=> Task.FromResult(new Resp());
}
}
""";
// Act
var result = RunGenerator(source);
// Assert
var generatedSource = GetGeneratedSource(result, "StellaEndpoints.g.cs");
generatedSource.Should().Contain($"Method = \"{method}\"");
}
#endregion
#region Error Cases Tests
[Fact]
public void Generator_WithAbstractClass_ReportsDiagnostic()
{
// Arrange
var source = """
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
namespace TestNamespace
{
public record Req();
public record Resp();
[StellaEndpoint("GET", "/abstract")]
public abstract class AbstractEndpoint : IStellaEndpoint<Req, Resp>
{
public abstract Task<Resp> HandleAsync(Req request, CancellationToken cancellationToken);
}
}
""";
// Act
var result = RunGenerator(source);
// Assert - Abstract classes are filtered at syntax level, so no diagnostic
// but also no generated code for this class
var generatedSource = GetGeneratedSource(result, "StellaEndpoints.g.cs");
generatedSource.Should().BeNull();
}
[Fact]
public void Generator_WithMissingInterface_ReportsDiagnostic()
{
// Arrange
var source = """
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
namespace TestNamespace
{
[StellaEndpoint("GET", "/no-interface")]
public class NoInterfaceEndpoint
{
public void Handle() { }
}
}
""";
// Act
var result = RunGenerator(source);
// Assert
var diagnostics = GetDiagnostics(result);
diagnostics.Should().Contain(d => d.Id == "STELLA001");
}
#endregion
#region Generated Provider Tests
[Fact]
public void Generator_GeneratesProviderClass()
{
// Arrange
var source = """
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
namespace TestNamespace
{
public record Req();
public record Resp();
[StellaEndpoint("GET", "/test")]
public class TestEndpoint : IStellaEndpoint<Req, Resp>
{
public Task<Resp> HandleAsync(Req request, CancellationToken cancellationToken)
=> Task.FromResult(new Resp());
}
}
""";
// Act
var result = RunGenerator(source);
// Assert
var providerSource = GetGeneratedSource(result, "GeneratedEndpointProvider.g.cs");
providerSource.Should().NotBeNullOrEmpty();
providerSource.Should().Contain("GeneratedEndpointProvider");
providerSource.Should().Contain("IGeneratedEndpointProvider");
providerSource.Should().Contain("GetEndpoints()");
providerSource.Should().Contain("RegisterHandlers");
providerSource.Should().Contain("GetHandlerTypes()");
}
#endregion
#region Namespace Tests
[Fact]
public void Generator_WithGlobalNamespace_HandlesCorrectly()
{
// Arrange
var source = """
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
public record Req();
public record Resp();
[StellaEndpoint("GET", "/global")]
public class GlobalEndpoint : IStellaEndpoint<Req, Resp>
{
public Task<Resp> HandleAsync(Req request, CancellationToken cancellationToken)
=> Task.FromResult(new Resp());
}
""";
// Act
var result = RunGenerator(source);
// Assert
var generatedSource = GetGeneratedSource(result, "StellaEndpoints.g.cs");
generatedSource.Should().NotBeNullOrEmpty();
generatedSource.Should().Contain("GlobalEndpoint");
}
[Fact]
public void Generator_WithNestedNamespace_HandlesCorrectly()
{
// Arrange
var source = """
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
namespace Company.Product.Module
{
public record Req();
public record Resp();
[StellaEndpoint("GET", "/nested")]
public class NestedEndpoint : IStellaEndpoint<Req, Resp>
{
public Task<Resp> HandleAsync(Req request, CancellationToken cancellationToken)
=> Task.FromResult(new Resp());
}
}
""";
// Act
var result = RunGenerator(source);
// Assert
var generatedSource = GetGeneratedSource(result, "StellaEndpoints.g.cs");
generatedSource.Should().NotBeNullOrEmpty();
generatedSource.Should().Contain("Company.Product.Module.NestedEndpoint");
}
#endregion
#region Path Escaping Tests
[Fact]
public void Generator_WithSpecialCharactersInPath_EscapesCorrectly()
{
// Arrange
var source = """
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Microservice;
namespace TestNamespace
{
public record Req();
public record Resp();
[StellaEndpoint("GET", "/users/{userId}/profile")]
public class ProfileEndpoint : IStellaEndpoint<Req, Resp>
{
public Task<Resp> HandleAsync(Req request, CancellationToken cancellationToken)
=> Task.FromResult(new Resp());
}
}
""";
// Act
var result = RunGenerator(source);
// Assert
var generatedSource = GetGeneratedSource(result, "StellaEndpoints.g.cs");
generatedSource.Should().Contain("/users/{userId}/profile");
}
#endregion
}

View File

@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>$(NoWarn);CA2255</NoWarn>
<IsPackable>false</IsPackable>
<RootNamespace>StellaOps.Microservice.SourceGen.Tests</RootNamespace>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
</PropertyGroup>
<ItemGroup>
<Using Include="Xunit" />
<Using Include="FluentAssertions" />
</ItemGroup>
<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="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\StellaOps.Microservice.SourceGen\StellaOps.Microservice.SourceGen.csproj" />
<ProjectReference Include="..\..\StellaOps.Microservice\StellaOps.Microservice.csproj" />
<ProjectReference Include="..\..\StellaOps.Router.Common\StellaOps.Router.Common.csproj" />
</ItemGroup>
</Project>