Implemented unit tests for QueueArguments attribute
Added proof-of-concept test for TapetiClient using Testcontainers.NET Updated packages
This commit is contained in:
parent
7143ad3c2f
commit
178f0a4956
@ -7,10 +7,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" Version="6.2.0" />
|
||||
<PackageReference Include="Castle.Windsor" Version="5.1.1" />
|
||||
<PackageReference Include="Ninject" Version="3.3.4" />
|
||||
<PackageReference Include="SimpleInjector" Version="5.3.0" />
|
||||
<PackageReference Include="Autofac" Version="6.5.0" />
|
||||
<PackageReference Include="Castle.Windsor" Version="5.1.2" />
|
||||
<PackageReference Include="Ninject" Version="3.3.6" />
|
||||
<PackageReference Include="SimpleInjector" Version="5.4.1" />
|
||||
<PackageReference Include="Unity" Version="5.11.10" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SimpleInjector" Version="5.3.0" />
|
||||
<PackageReference Include="SimpleInjector" Version="5.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SimpleInjector" Version="5.3.0" />
|
||||
<PackageReference Include="SimpleInjector" Version="5.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SimpleInjector" Version="5.3.0" />
|
||||
<PackageReference Include="SimpleInjector" Version="5.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SimpleInjector" Version="5.3.0" />
|
||||
<PackageReference Include="SimpleInjector" Version="5.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SimpleInjector" Version="5.3.0" />
|
||||
<PackageReference Include="SimpleInjector" Version="5.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SimpleInjector" Version="5.3.0" />
|
||||
<PackageReference Include="SimpleInjector" Version="5.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -7,8 +7,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
|
||||
<PackageReference Include="SimpleInjector" Version="5.3.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||
<PackageReference Include="SimpleInjector" Version="5.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -15,7 +15,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" Version="6.2.0" />
|
||||
<PackageReference Include="Autofac" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -30,6 +30,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -15,7 +15,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Castle.Windsor" Version="5.1.1" />
|
||||
<PackageReference Include="Castle.Windsor" Version="5.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -30,6 +30,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -34,6 +34,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -32,7 +32,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -48,6 +48,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -35,7 +35,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
<PackageReference Include="Tapeti.Annotations" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -15,7 +15,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ninject" Version="3.3.4" />
|
||||
<PackageReference Include="Ninject" Version="3.3.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -30,6 +30,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -19,7 +19,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Serilog" Version="2.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -34,6 +34,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -19,7 +19,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SimpleInjector" Version="5.3.0" />
|
||||
<PackageReference Include="SimpleInjector" Version="5.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -34,6 +34,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
79
Tapeti.Tests/Client/RabbitMQFixture.cs
Normal file
79
Tapeti.Tests/Client/RabbitMQFixture.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Docker.DotNet;
|
||||
using Docker.DotNet.Models;
|
||||
using DotNet.Testcontainers.Builders;
|
||||
using DotNet.Testcontainers.Configurations;
|
||||
using DotNet.Testcontainers.Containers;
|
||||
using Xunit;
|
||||
|
||||
namespace Tapeti.Tests.Client
|
||||
{
|
||||
[CollectionDefinition(Name)]
|
||||
public sealed class RabbitMQCollection : ICollectionFixture<RabbitMQFixture>
|
||||
{
|
||||
public const string Name = "RabbitMQ";
|
||||
}
|
||||
|
||||
|
||||
public sealed class RabbitMQFixture : IAsyncLifetime
|
||||
{
|
||||
public static string RabbitMQUsername => "tapetitests";
|
||||
public static string RabbitMQPassword => "topsecret1234";
|
||||
|
||||
public ushort RabbitMQPort { get; private set; }
|
||||
public ushort RabbitMQManagementPort { get; private set; }
|
||||
|
||||
|
||||
private TestcontainerMessageBroker testcontainers;
|
||||
|
||||
private const int DefaultRabbitMQPort = 5672;
|
||||
private const int DefaultRabbitMQManagementPort = 15672;
|
||||
|
||||
|
||||
|
||||
private const string ImageName = "rabbitmq";
|
||||
private const string ImageTag = "3.11.3-alpine";
|
||||
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Testcontainers does not seem to pull the image the first time.
|
||||
// I didn't get it to work, even using WithImagePullPolicy from the latest beta.
|
||||
// Note: running it the first time can take a while.
|
||||
var client = new DockerClientConfiguration().CreateClient();
|
||||
await client.Images.CreateImageAsync(
|
||||
new ImagesCreateParameters
|
||||
{
|
||||
FromImage = ImageName,
|
||||
Tag = ImageTag
|
||||
},
|
||||
null,
|
||||
new Progress<JSONMessage>());
|
||||
|
||||
// If you get a "Sequence contains no elements" error here: make sure Docker Desktop is running
|
||||
var testcontainersBuilder = new TestcontainersBuilder<RabbitMqTestcontainer>()
|
||||
.WithMessageBroker(new RabbitMqTestcontainerConfiguration($"{ImageName}:{ImageTag}")
|
||||
{
|
||||
Username = RabbitMQUsername,
|
||||
Password = RabbitMQPassword
|
||||
})
|
||||
.WithExposedPort(DefaultRabbitMQManagementPort)
|
||||
.WithPortBinding(0, DefaultRabbitMQManagementPort);
|
||||
|
||||
testcontainers = testcontainersBuilder.Build();
|
||||
|
||||
await testcontainers.StartAsync();
|
||||
|
||||
RabbitMQPort = testcontainers.GetMappedPublicPort(DefaultRabbitMQPort);
|
||||
RabbitMQManagementPort = testcontainers.GetMappedPublicPort(DefaultRabbitMQManagementPort);
|
||||
}
|
||||
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
if (testcontainers != null)
|
||||
await testcontainers.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
75
Tapeti.Tests/Client/TapetiClientTests.cs
Normal file
75
Tapeti.Tests/Client/TapetiClientTests.cs
Normal file
@ -0,0 +1,75 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Tapeti.Connection;
|
||||
using Tapeti.Tests.Mock;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Tapeti.Tests.Client
|
||||
{
|
||||
[Collection(RabbitMQCollection.Name)]
|
||||
public class TapetiClientTests
|
||||
{
|
||||
private readonly RabbitMQFixture fixture;
|
||||
private readonly MockDependencyResolver dependencyResolver = new();
|
||||
|
||||
public TapetiClientTests(RabbitMQFixture fixture, ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
this.fixture = fixture;
|
||||
|
||||
dependencyResolver.Set<ILogger>(new MockLogger(testOutputHelper));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Fixture()
|
||||
{
|
||||
fixture.RabbitMQPort.Should().BeGreaterThan(0);
|
||||
fixture.RabbitMQManagementPort.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task DynamicQueueDeclareNoPrefix()
|
||||
{
|
||||
var client = CreateCilent();
|
||||
|
||||
var queueName = await client.DynamicQueueDeclare(null, null, CancellationToken.None);
|
||||
queueName.Should().NotBeNullOrEmpty();
|
||||
|
||||
await client.Close();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task DynamicQueueDeclarePrefix()
|
||||
{
|
||||
var client = CreateCilent();
|
||||
|
||||
var queueName = await client.DynamicQueueDeclare("dynamicprefix", null, CancellationToken.None);
|
||||
queueName.Should().StartWith("dynamicprefix");
|
||||
|
||||
await client.Close();
|
||||
}
|
||||
|
||||
|
||||
// TODO test the other methods
|
||||
|
||||
|
||||
private TapetiClient CreateCilent()
|
||||
{
|
||||
return new TapetiClient(
|
||||
new TapetiConfig.Config(dependencyResolver),
|
||||
new TapetiConnectionParams
|
||||
{
|
||||
HostName = "127.0.0.1",
|
||||
Port = fixture.RabbitMQPort,
|
||||
ManagementPort = fixture.RabbitMQManagementPort,
|
||||
Username = RabbitMQFixture.RabbitMQUsername,
|
||||
Password = RabbitMQFixture.RabbitMQPassword,
|
||||
PrefetchCount = 50
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
29
Tapeti.Tests/Config/BaseControllerTest.cs
Normal file
29
Tapeti.Tests/Config/BaseControllerTest.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using JetBrains.Annotations;
|
||||
using Tapeti.Config;
|
||||
using Tapeti.Tests.Mock;
|
||||
|
||||
namespace Tapeti.Tests.Config
|
||||
{
|
||||
public class BaseControllerTest
|
||||
{
|
||||
protected readonly MockDependencyResolver DependencyResolver = new();
|
||||
|
||||
|
||||
protected ITapetiConfig GetControllerConfig<[MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] T>() where T : class
|
||||
{
|
||||
var configBuilder = new TapetiConfig(DependencyResolver);
|
||||
|
||||
configBuilder.EnableDeclareDurableQueues();
|
||||
configBuilder.RegisterController(typeof(T));
|
||||
var config = configBuilder.Build();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
protected ITapetiConfigBindings GetControllerBindings<[MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] T>() where T : class
|
||||
{
|
||||
return GetControllerConfig<T>().Bindings;
|
||||
}
|
||||
}
|
||||
}
|
199
Tapeti.Tests/Config/QueueArgumentsTest.cs
Normal file
199
Tapeti.Tests/Config/QueueArgumentsTest.cs
Normal file
@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using FluentAssertions.Execution;
|
||||
using Moq;
|
||||
using Tapeti.Annotations;
|
||||
using Tapeti.Config;
|
||||
using Tapeti.Connection;
|
||||
using Xunit;
|
||||
|
||||
namespace Tapeti.Tests.Config
|
||||
{
|
||||
public class QueueArgumentsTest : BaseControllerTest
|
||||
{
|
||||
private static readonly MockRepository MoqRepository = new(MockBehavior.Strict);
|
||||
|
||||
private readonly Mock<ITapetiClient> client;
|
||||
private readonly Dictionary<string, IReadOnlyDictionary<string, string>> declaredQueues = new();
|
||||
|
||||
|
||||
public QueueArgumentsTest()
|
||||
{
|
||||
client = MoqRepository.Create<ITapetiClient>();
|
||||
var routingKeyStrategy = MoqRepository.Create<IRoutingKeyStrategy>();
|
||||
var exchangeStrategy = MoqRepository.Create<IExchangeStrategy>();
|
||||
|
||||
DependencyResolver.Set(routingKeyStrategy.Object);
|
||||
DependencyResolver.Set(exchangeStrategy.Object);
|
||||
|
||||
|
||||
routingKeyStrategy
|
||||
.Setup(s => s.GetRoutingKey(typeof(TestMessage1)))
|
||||
.Returns("testmessage1");
|
||||
|
||||
routingKeyStrategy
|
||||
.Setup(s => s.GetRoutingKey(typeof(TestMessage2)))
|
||||
.Returns("testmessage2");
|
||||
|
||||
exchangeStrategy
|
||||
.Setup(s => s.GetExchange(It.IsAny<Type>()))
|
||||
.Returns("exchange");
|
||||
|
||||
var queue = 0;
|
||||
client
|
||||
.Setup(c => c.DynamicQueueDeclare(null, It.IsAny<IReadOnlyDictionary<string, string>>(), It.IsAny<CancellationToken>()))
|
||||
.Callback((string _, IReadOnlyDictionary<string, string> arguments, CancellationToken _) =>
|
||||
{
|
||||
queue++;
|
||||
declaredQueues.Add($"queue-{queue}", arguments);
|
||||
})
|
||||
.ReturnsAsync(() => $"queue-{queue}");
|
||||
|
||||
client
|
||||
.Setup(c => c.DurableQueueDeclare(It.IsAny<string>(), It.IsAny<IEnumerable<QueueBinding>>(), It.IsAny<IReadOnlyDictionary<string, string>>(), It.IsAny<CancellationToken>()))
|
||||
.Callback((string queueName, IEnumerable<QueueBinding> _, IReadOnlyDictionary<string, string> arguments, CancellationToken _) =>
|
||||
{
|
||||
declaredQueues.Add(queueName, arguments);
|
||||
})
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
|
||||
client
|
||||
.Setup(c => c.DynamicQueueBind(It.IsAny<string>(), It.IsAny<QueueBinding>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task SingleQueueArguments()
|
||||
{
|
||||
var config = GetControllerConfig<TestController>();
|
||||
|
||||
var binding1 = config.Bindings.Single(b => b is IControllerMethodBinding cmb && cmb.Method.Name == "HandleMessage1");
|
||||
binding1.Should().NotBeNull();
|
||||
|
||||
var binding2 = config.Bindings.Single(b => b is IControllerMethodBinding cmb && cmb.Method.Name == "HandleMessage2");
|
||||
binding2.Should().NotBeNull();
|
||||
|
||||
|
||||
|
||||
var subscriber = new TapetiSubscriber(() => client.Object, config);
|
||||
await subscriber.ApplyBindings();
|
||||
|
||||
|
||||
declaredQueues.Should().HaveCount(1);
|
||||
var arguments = declaredQueues["queue-1"];
|
||||
|
||||
arguments.Should().ContainKey("x-custom").WhoseValue.Should().Be("custom value");
|
||||
arguments.Should().ContainKey("x-another").WhoseValue.Should().Be("another value");
|
||||
arguments.Should().ContainKey("x-max-length").WhoseValue.Should().Be("100");
|
||||
arguments.Should().ContainKey("x-max-length-bytes").WhoseValue.Should().Be("100000");
|
||||
arguments.Should().ContainKey("x-message-ttl").WhoseValue.Should().Be("4269");
|
||||
arguments.Should().ContainKey("x-overflow").WhoseValue.Should().Be("reject-publish");
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task ConflictingDynamicQueueArguments()
|
||||
{
|
||||
var config = GetControllerConfig<ConflictingArgumentsTestController>();
|
||||
|
||||
var subscriber = new TapetiSubscriber(() => client.Object, config);
|
||||
await subscriber.ApplyBindings();
|
||||
|
||||
declaredQueues.Should().HaveCount(2);
|
||||
|
||||
var arguments1 = declaredQueues["queue-1"];
|
||||
arguments1.Should().ContainKey("x-max-length").WhoseValue.Should().Be("100");
|
||||
|
||||
var arguments2 = declaredQueues["queue-2"];
|
||||
arguments2.Should().ContainKey("x-max-length-bytes").WhoseValue.Should().Be("100000");
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task ConflictingDurableQueueArguments()
|
||||
{
|
||||
var config = GetControllerConfig<ConflictingArgumentsDurableQueueTestController>();
|
||||
|
||||
var testApplyBindings = () =>
|
||||
{
|
||||
var subscriber = new TapetiSubscriber(() => client.Object, config);
|
||||
return subscriber.ApplyBindings();
|
||||
};
|
||||
|
||||
using (new AssertionScope())
|
||||
{
|
||||
await testApplyBindings.Should().ThrowAsync<TopologyConfigurationException>();
|
||||
declaredQueues.Should().HaveCount(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ReSharper disable all
|
||||
#pragma warning disable
|
||||
|
||||
private class TestMessage1
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
private class TestMessage2
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[DynamicQueue]
|
||||
[QueueArguments("x-custom", "custom value", "x-another", "another value", MaxLength = 100, MaxLengthBytes = 100000, MessageTTL = 4269, Overflow = RabbitMQOverflow.RejectPublish)]
|
||||
private class TestController
|
||||
{
|
||||
public void HandleMessage1(TestMessage1 message)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public void HandleMessage2(TestMessage2 message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[DynamicQueue]
|
||||
[QueueArguments(MaxLength = 100)]
|
||||
private class ConflictingArgumentsTestController
|
||||
{
|
||||
public void HandleMessage1(TestMessage1 message)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[QueueArguments(MaxLengthBytes = 100000)]
|
||||
public void HandleMessage2(TestMessage1 message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[DurableQueue("durable")]
|
||||
[QueueArguments(MaxLength = 100)]
|
||||
private class ConflictingArgumentsDurableQueueTestController
|
||||
{
|
||||
public void HandleMessage1(TestMessage1 message)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[QueueArguments(MaxLengthBytes = 100000)]
|
||||
public void HandleMessage2(TestMessage1 message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore
|
||||
// ReSharper restore all
|
||||
}
|
||||
}
|
43
Tapeti.Tests/Config/SimpleControllerTest.cs
Normal file
43
Tapeti.Tests/Config/SimpleControllerTest.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Tapeti.Annotations;
|
||||
using Tapeti.Config;
|
||||
using Xunit;
|
||||
|
||||
namespace Tapeti.Tests.Config
|
||||
{
|
||||
public class SimpleControllerTest : BaseControllerTest
|
||||
{
|
||||
[Fact]
|
||||
public void RegisterController()
|
||||
{
|
||||
var bindings = GetControllerBindings<TestController>();
|
||||
bindings.Should().HaveCount(1);
|
||||
|
||||
var handleSimpleMessageBinding = bindings.Single(b => b is IControllerMethodBinding cmb &&
|
||||
cmb.Controller == typeof(TestController) &&
|
||||
cmb.Method.Name == "HandleSimpleMessage");
|
||||
handleSimpleMessageBinding.QueueType.Should().Be(QueueType.Dynamic);
|
||||
}
|
||||
|
||||
|
||||
// ReSharper disable all
|
||||
#pragma warning disable
|
||||
|
||||
private class TestMessage
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[DynamicQueue]
|
||||
private class TestController
|
||||
{
|
||||
public void HandleSimpleMessage(TestMessage message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore
|
||||
// ReSharper restore all
|
||||
}
|
||||
}
|
28
Tapeti.Tests/Mock/MockDependencyResolver.cs
Normal file
28
Tapeti.Tests/Mock/MockDependencyResolver.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Tapeti.Tests.Mock
|
||||
{
|
||||
public class MockDependencyResolver : IDependencyResolver
|
||||
{
|
||||
private readonly Dictionary<Type, object> container = new();
|
||||
|
||||
|
||||
public void Set<TInterface>(TInterface instance)
|
||||
{
|
||||
container.Add(typeof(TInterface), instance);
|
||||
}
|
||||
|
||||
|
||||
public T Resolve<T>() where T : class
|
||||
{
|
||||
return (T)Resolve(typeof(T));
|
||||
}
|
||||
|
||||
|
||||
public object Resolve(Type type)
|
||||
{
|
||||
return container[type];
|
||||
}
|
||||
}
|
||||
}
|
88
Tapeti.Tests/Mock/MockLogger.cs
Normal file
88
Tapeti.Tests/Mock/MockLogger.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Tapeti.Config;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Tapeti.Tests.Mock
|
||||
{
|
||||
internal class MockLogger : IBindingLogger
|
||||
{
|
||||
private readonly ITestOutputHelper testOutputHelper;
|
||||
|
||||
|
||||
public MockLogger(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
this.testOutputHelper = testOutputHelper;
|
||||
}
|
||||
|
||||
|
||||
public void Connect(IConnectContext connectContext)
|
||||
{
|
||||
testOutputHelper.WriteLine($"{(connectContext.IsReconnect ? "Reconnecting" : "Connecting")} to {connectContext.ConnectionParams.HostName}:{connectContext.ConnectionParams.Port}{connectContext.ConnectionParams.VirtualHost}");
|
||||
}
|
||||
|
||||
public void ConnectFailed(IConnectFailedContext connectContext)
|
||||
{
|
||||
testOutputHelper.WriteLine($"Connection failed: {connectContext.Exception}");
|
||||
}
|
||||
|
||||
public void ConnectSuccess(IConnectSuccessContext connectContext)
|
||||
{
|
||||
testOutputHelper.WriteLine($"{(connectContext.IsReconnect ? "Reconnected" : "Connected")} using local port {connectContext.LocalPort}");
|
||||
}
|
||||
|
||||
public void Disconnect(IDisconnectContext disconnectContext)
|
||||
{
|
||||
testOutputHelper.WriteLine($"Connection closed: {(!string.IsNullOrEmpty(disconnectContext.ReplyText) ? disconnectContext.ReplyText : "<no reply text>")} (reply code: {disconnectContext.ReplyCode})");
|
||||
}
|
||||
|
||||
public void ConsumeException(Exception exception, IMessageContext messageContext, ConsumeResult consumeResult)
|
||||
{
|
||||
testOutputHelper.WriteLine(exception.Message);
|
||||
}
|
||||
|
||||
public void QueueDeclare(string queueName, bool durable, bool passive)
|
||||
{
|
||||
testOutputHelper.WriteLine(passive
|
||||
? $"Verifying durable queue {queueName}"
|
||||
: $"Declaring {(durable ? "durable" : "dynamic")} queue {queueName}");
|
||||
}
|
||||
|
||||
public void QueueExistsWarning(string queueName, IReadOnlyDictionary<string, string> existingArguments, IReadOnlyDictionary<string, string> arguments)
|
||||
{
|
||||
var argumentsText = new StringBuilder();
|
||||
foreach (var pair in arguments)
|
||||
{
|
||||
if (argumentsText.Length > 0)
|
||||
argumentsText.Append(", ");
|
||||
|
||||
argumentsText.Append($"{pair.Key} = {pair.Value}");
|
||||
}
|
||||
|
||||
testOutputHelper.WriteLine($"Durable queue {queueName} exists with incompatible x-arguments ({argumentsText}) and will not be redeclared, queue will be consumed as-is");
|
||||
}
|
||||
|
||||
public void QueueBind(string queueName, bool durable, string exchange, string routingKey)
|
||||
{
|
||||
testOutputHelper.WriteLine($"Binding {queueName} to exchange {exchange} with routing key {routingKey}");
|
||||
}
|
||||
|
||||
public void QueueUnbind(string queueName, string exchange, string routingKey)
|
||||
{
|
||||
testOutputHelper.WriteLine($"Removing binding for {queueName} to exchange {exchange} with routing key {routingKey}");
|
||||
}
|
||||
|
||||
public void ExchangeDeclare(string exchange)
|
||||
{
|
||||
testOutputHelper.WriteLine($"Declaring exchange {exchange}");
|
||||
}
|
||||
|
||||
public void QueueObsolete(string queueName, bool deleted, uint messageCount)
|
||||
{
|
||||
testOutputHelper.WriteLine(deleted
|
||||
? $"Obsolete queue was deleted: {queueName}"
|
||||
: $"Obsolete queue bindings removed: {queueName}, {messageCount} messages remaining");
|
||||
}
|
||||
}
|
||||
}
|
@ -15,10 +15,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PackageReference Include="FluentAssertions" Version="6.8.0" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.2" />
|
||||
<PackageReference Include="Testcontainers" Version="2.2.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -30,6 +30,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -30,6 +30,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -118,7 +118,7 @@ namespace Tapeti.Config
|
||||
/// before the configuration is built. Implementations of ITapetiConfigBuilder should also implement this interface.
|
||||
/// Should not be used outside of Tapeti packages.
|
||||
/// </summary>
|
||||
public interface ITapetiConfigBuilderAccess
|
||||
public interface ITapetiConfigBuilderAccess : ITapetiConfigBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to the dependency resolver.
|
||||
|
3
Tapeti/Properties/AssemblyInfo.cs
Normal file
3
Tapeti/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Tapeti.Tests")]
|
@ -24,8 +24,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.2.1" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.4.0" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
|
||||
@ -41,7 +41,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
<PackageReference Include="Tapeti.Annotations" Version="3.*-*" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -15,7 +15,7 @@ namespace Tapeti
|
||||
/// Default implementation of the Tapeti config builder.
|
||||
/// Automatically registers the default middleware for injecting the message parameter and handling the return value.
|
||||
/// </summary>
|
||||
public class TapetiConfig : ITapetiConfigBuilder, ITapetiConfigBuilderAccess
|
||||
public class TapetiConfig : ITapetiConfigBuilderAccess
|
||||
{
|
||||
private Config config;
|
||||
private readonly List<IControllerBindingMiddleware> bindingMiddleware = new();
|
||||
|
@ -38,7 +38,7 @@ namespace Tapeti
|
||||
if (!controller.IsClass)
|
||||
throw new ArgumentException($"Controller {controller.Name} must be a class");
|
||||
|
||||
var controllerQueueInfo = GetQueueInfo(controller);
|
||||
var controllerQueueInfo = GetQueueInfo(controller, null);
|
||||
(builderAccess.DependencyResolver as IDependencyContainer)?.RegisterController(controller);
|
||||
|
||||
var controllerIsObsolete = controller.GetCustomAttribute<ObsoleteAttribute>() != null;
|
||||
@ -79,7 +79,7 @@ namespace Tapeti
|
||||
throw new TopologyConfigurationException($"Method {method.Name} in controller {method.DeclaringType?.Name} has unknown parameters: {parameterNames}");
|
||||
}
|
||||
|
||||
var methodQueueInfo = GetQueueInfo(method) ?? controllerQueueInfo;
|
||||
var methodQueueInfo = GetQueueInfo(method, controllerQueueInfo);
|
||||
if (methodQueueInfo is not { IsValid: true })
|
||||
throw new TopologyConfigurationException(
|
||||
$"Method {method.Name} or controller {controller.Name} requires a queue attribute");
|
||||
@ -129,23 +129,45 @@ namespace Tapeti
|
||||
}
|
||||
|
||||
|
||||
private static ControllerMethodBinding.QueueInfo GetQueueInfo(MemberInfo member)
|
||||
private static ControllerMethodBinding.QueueInfo GetQueueInfo(MemberInfo member, ControllerMethodBinding.QueueInfo fallbackQueueInfo)
|
||||
{
|
||||
var dynamicQueueAttribute = member.GetCustomAttribute<DynamicQueueAttribute>();
|
||||
var durableQueueAttribute = member.GetCustomAttribute<DurableQueueAttribute>();
|
||||
var queueArgumentsAttribute = member.GetCustomAttribute<QueueArgumentsAttribute>();
|
||||
|
||||
if (dynamicQueueAttribute != null && durableQueueAttribute != null)
|
||||
throw new TopologyConfigurationException($"Cannot combine static and dynamic queue attributes on controller {member.DeclaringType?.Name} method {member.Name}");
|
||||
|
||||
if (dynamicQueueAttribute == null && durableQueueAttribute == null && (queueArgumentsAttribute == null || fallbackQueueInfo == null))
|
||||
return fallbackQueueInfo;
|
||||
|
||||
var queueArgumentsAttribute = member.GetCustomAttribute<QueueArgumentsAttribute>();
|
||||
|
||||
QueueType queueType;
|
||||
string name;
|
||||
|
||||
|
||||
if (dynamicQueueAttribute != null)
|
||||
return new ControllerMethodBinding.QueueInfo { QueueType = QueueType.Dynamic, Name = dynamicQueueAttribute.Prefix, QueueArguments = GetQueueArguments(queueArgumentsAttribute) };
|
||||
{
|
||||
queueType = QueueType.Dynamic;
|
||||
name = dynamicQueueAttribute.Prefix;
|
||||
}
|
||||
else if (durableQueueAttribute != null)
|
||||
{
|
||||
queueType = QueueType.Durable;
|
||||
name = durableQueueAttribute.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
queueType = fallbackQueueInfo.QueueType;
|
||||
name = fallbackQueueInfo.Name;
|
||||
}
|
||||
|
||||
return durableQueueAttribute != null
|
||||
? new ControllerMethodBinding.QueueInfo { QueueType = QueueType.Durable, Name = durableQueueAttribute.Name, QueueArguments = GetQueueArguments(queueArgumentsAttribute) }
|
||||
: null;
|
||||
return new ControllerMethodBinding.QueueInfo
|
||||
{
|
||||
QueueType = queueType,
|
||||
Name = name,
|
||||
QueueArguments = GetQueueArguments(queueArgumentsAttribute) ?? fallbackQueueInfo?.QueueArguments
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user