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,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
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user