diff --git a/.circleci/config.yml b/.circleci/config.yml index 3f64d636..660cf937 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,10 +30,22 @@ jobs: steps: - checkout - run: - name: Build and test + name: Build command: | dotnet build - dotnet test --no-build + - run: + name: Unit tests + command: | + dotnet test --filter "Category!=E2E&Category!=Integration" + - run: + name: Integration tests + command: | + dotnet test --filter "Category=Integration" + + #- run: # skipping E2E tests for now + # name: E2E tests + # command: | + # dotnet test --filter "Category=E2E" sonarscan: docker: @@ -86,7 +98,7 @@ jobs: name: Build and test command: | dotnet build - dotnet test --no-build /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput=coverage.opencover.xml + dotnet test --no-build --filter "Category!=E2E" /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput=coverage.opencover.xml - run: name: Stop Sonarcloud diff --git a/CHANGELOG.md b/CHANGELOG.md index 4935da29..75ccecdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. +## [2021-03-26](https://github.com/mizrael/OpenSleigh/pull/32) +### Added +- major refactoring +- moved E2E tests to standalone project +### Fixed +- addressed minor consistency issue +- minor bugfixes + ## [2021-03-23](https://github.com/mizrael/OpenSleigh/pull/31) ### Added - added Kafka transport library diff --git a/OpenSleigh.sln b/OpenSleigh.sln index 0f043143..1a14db42 100644 --- a/OpenSleigh.sln +++ b/OpenSleigh.sln @@ -39,9 +39,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSleigh.Persistence.Cosm EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSleigh.Persistence.Cosmos.SQL.Tests", "tests\OpenSleigh.Persistence.Cosmos.SQL.Tests\OpenSleigh.Persistence.Cosmos.SQL.Tests.csproj", "{08E996DB-3D0C-4A63-8166-BF61732D3B21}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSleigh.Transport.Kafka", "src\OpenSleigh.Transport.Kafka\OpenSleigh.Transport.Kafka.csproj", "{887358C5-9EFF-4498-A04B-E12B199EC259}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSleigh.Transport.Kafka", "src\OpenSleigh.Transport.Kafka\OpenSleigh.Transport.Kafka.csproj", "{887358C5-9EFF-4498-A04B-E12B199EC259}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSleigh.Transport.Kafka.Tests", "tests\OpenSleigh.Transport.Kafka.Tests\OpenSleigh.Transport.Kafka.Tests.csproj", "{5B363808-664B-42F4-8C38-EEFB9F05C500}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSleigh.Transport.Kafka.Tests", "tests\OpenSleigh.Transport.Kafka.Tests\OpenSleigh.Transport.Kafka.Tests.csproj", "{5B363808-664B-42F4-8C38-EEFB9F05C500}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSleigh.E2ETests", "tests\OpenSleigh.E2ETests\OpenSleigh.E2ETests.csproj", "{A14A1452-A758-4CF3-83CC-E1C6897D83ED}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -121,6 +123,10 @@ Global {5B363808-664B-42F4-8C38-EEFB9F05C500}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B363808-664B-42F4-8C38-EEFB9F05C500}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B363808-664B-42F4-8C38-EEFB9F05C500}.Release|Any CPU.Build.0 = Release|Any CPU + {A14A1452-A758-4CF3-83CC-E1C6897D83ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A14A1452-A758-4CF3-83CC-E1C6897D83ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A14A1452-A758-4CF3-83CC-E1C6897D83ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A14A1452-A758-4CF3-83CC-E1C6897D83ED}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/Sample1/OpenSleigh.Samples.Sample1.Console/Sagas/MySaga.cs b/samples/Sample1/OpenSleigh.Samples.Sample1.Console/Sagas/MySaga.cs index c2ca5c81..2b72eda7 100644 --- a/samples/Sample1/OpenSleigh.Samples.Sample1.Console/Sagas/MySaga.cs +++ b/samples/Sample1/OpenSleigh.Samples.Sample1.Console/Sagas/MySaga.cs @@ -35,7 +35,7 @@ public async Task HandleAsync(IMessageContext context, CancellationTo _logger.LogInformation($"starting saga '{context.Message.CorrelationId}'..."); var message = new ProcessMySaga(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) @@ -43,7 +43,7 @@ public async Task HandleAsync(IMessageContext context, Cancellati _logger.LogInformation($"processing saga '{context.Message.CorrelationId}'..."); var message = new MySagaCompleted(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) diff --git a/samples/Sample2/OpenSleigh.Samples.Sample2.Common/Sagas/ChildSaga.cs b/samples/Sample2/OpenSleigh.Samples.Sample2.Common/Sagas/ChildSaga.cs index cbb5560f..a1bec45b 100644 --- a/samples/Sample2/OpenSleigh.Samples.Sample2.Common/Sagas/ChildSaga.cs +++ b/samples/Sample2/OpenSleigh.Samples.Sample2.Common/Sagas/ChildSaga.cs @@ -38,7 +38,7 @@ public async Task HandleAsync(IMessageContext context, Cancellat await Task.Delay(TimeSpan.FromSeconds(_random.Next(1, 5)), cancellationToken); var message = new ProcessChildSaga(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) @@ -50,7 +50,7 @@ public async Task HandleAsync(IMessageContext context, Cancell _logger.LogInformation($"child saga '{context.Message.CorrelationId}' completed!"); var completedEvent = new ChildSagaCompleted(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(completedEvent, cancellationToken); + this.Publish(completedEvent); } } } diff --git a/samples/Sample2/OpenSleigh.Samples.Sample2.Common/Sagas/ParentSaga.cs b/samples/Sample2/OpenSleigh.Samples.Sample2.Common/Sagas/ParentSaga.cs index 95cd6279..a634fad0 100644 --- a/samples/Sample2/OpenSleigh.Samples.Sample2.Common/Sagas/ParentSaga.cs +++ b/samples/Sample2/OpenSleigh.Samples.Sample2.Common/Sagas/ParentSaga.cs @@ -38,7 +38,7 @@ public async Task HandleAsync(IMessageContext context, Cancella _logger.LogInformation($"starting parent saga '{context.Message.CorrelationId}'..."); var message = new ProcessParentSaga(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) @@ -46,7 +46,7 @@ public async Task HandleAsync(IMessageContext context, Cancel _logger.LogInformation($"starting child saga from parent saga '{context.Message.CorrelationId}'..."); var message = new StartChildSaga(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) @@ -56,7 +56,7 @@ public async Task HandleAsync(IMessageContext context, Cance await Task.Delay(TimeSpan.FromSeconds(_random.Next(1, 5)), cancellationToken); var message = new ParentSagaCompleted(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) diff --git a/samples/Sample2/OpenSleigh.Samples.Sample2.Common/Sagas/SimpleSaga.cs b/samples/Sample2/OpenSleigh.Samples.Sample2.Common/Sagas/SimpleSaga.cs index d44a4340..72bd2349 100644 --- a/samples/Sample2/OpenSleigh.Samples.Sample2.Common/Sagas/SimpleSaga.cs +++ b/samples/Sample2/OpenSleigh.Samples.Sample2.Common/Sagas/SimpleSaga.cs @@ -35,7 +35,7 @@ public async Task HandleAsync(IMessageContext context, Cancella _logger.LogInformation($"starting saga '{context.Message.CorrelationId}'..."); var message = new ProcessSimpleSaga(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) @@ -43,7 +43,7 @@ public async Task HandleAsync(IMessageContext context, Cancel _logger.LogInformation($"processing saga '{context.Message.CorrelationId}'..."); var message = new SimpleSagaCompleted(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) diff --git a/samples/Sample3/OpenSleigh.Samples.Sample3.Common/Sagas/ChildSaga.cs b/samples/Sample3/OpenSleigh.Samples.Sample3.Common/Sagas/ChildSaga.cs index 69686557..ab442ea7 100644 --- a/samples/Sample3/OpenSleigh.Samples.Sample3.Common/Sagas/ChildSaga.cs +++ b/samples/Sample3/OpenSleigh.Samples.Sample3.Common/Sagas/ChildSaga.cs @@ -38,19 +38,20 @@ public async Task HandleAsync(IMessageContext context, Cancellat await Task.Delay(TimeSpan.FromSeconds(_random.Next(1, 5)), cancellationToken); var message = new ProcessChildSaga(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } - public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) + public async Task HandleAsync(IMessageContext context, + CancellationToken cancellationToken = default) { _logger.LogInformation($"processing child saga '{context.Message.CorrelationId}'..."); - + await Task.Delay(TimeSpan.FromSeconds(_random.Next(1, 5)), cancellationToken); - + _logger.LogInformation($"child saga '{context.Message.CorrelationId}' completed!"); var completedEvent = new ChildSagaCompleted(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(completedEvent, cancellationToken); + this.Publish(completedEvent); } } } diff --git a/samples/Sample3/OpenSleigh.Samples.Sample3.Common/Sagas/ParentSaga.cs b/samples/Sample3/OpenSleigh.Samples.Sample3.Common/Sagas/ParentSaga.cs index 07924c84..8b2a61f3 100644 --- a/samples/Sample3/OpenSleigh.Samples.Sample3.Common/Sagas/ParentSaga.cs +++ b/samples/Sample3/OpenSleigh.Samples.Sample3.Common/Sagas/ParentSaga.cs @@ -38,7 +38,7 @@ public async Task HandleAsync(IMessageContext context, Cancella _logger.LogInformation($"starting parent saga '{context.Message.CorrelationId}'..."); var message = new ProcessParentSaga(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) @@ -46,7 +46,7 @@ public async Task HandleAsync(IMessageContext context, Cancel _logger.LogInformation($"starting child saga from parent saga '{context.Message.CorrelationId}'..."); var message = new StartChildSaga(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) @@ -56,7 +56,7 @@ public async Task HandleAsync(IMessageContext context, Cance await Task.Delay(TimeSpan.FromSeconds(_random.Next(1, 5)), cancellationToken); var message = new ParentSagaCompleted(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) diff --git a/samples/Sample3/OpenSleigh.Samples.Sample3.Common/Sagas/SimpleSaga.cs b/samples/Sample3/OpenSleigh.Samples.Sample3.Common/Sagas/SimpleSaga.cs index 60f25cf3..63157a1a 100644 --- a/samples/Sample3/OpenSleigh.Samples.Sample3.Common/Sagas/SimpleSaga.cs +++ b/samples/Sample3/OpenSleigh.Samples.Sample3.Common/Sagas/SimpleSaga.cs @@ -35,7 +35,7 @@ public async Task HandleAsync(IMessageContext context, Cancella _logger.LogInformation($"starting saga '{context.Message.CorrelationId}'..."); var message = new ProcessSimpleSaga(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) @@ -43,7 +43,7 @@ public async Task HandleAsync(IMessageContext context, Cancel _logger.LogInformation($"processing saga '{context.Message.CorrelationId}'..."); var message = new SimpleSagaCompleted(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) diff --git a/samples/Sample4/OpenSleigh.Samples.Sample4.InventoryService/Sagas/InventoryCheckSaga.cs b/samples/Sample4/OpenSleigh.Samples.Sample4.InventoryService/Sagas/InventoryCheckSaga.cs index 8c720654..128fe950 100644 --- a/samples/Sample4/OpenSleigh.Samples.Sample4.InventoryService/Sagas/InventoryCheckSaga.cs +++ b/samples/Sample4/OpenSleigh.Samples.Sample4.InventoryService/Sagas/InventoryCheckSaga.cs @@ -28,7 +28,7 @@ public async Task HandleAsync(IMessageContext context, Cancellat _logger.LogInformation($"checking inventory for order '{context.Message.OrderId}'..."); var message = InventoryCheckCompleted.New(context.Message.OrderId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); this.State.MarkAsCompleted(); } diff --git a/samples/Sample4/OpenSleigh.Samples.Sample4.Orchestrator/Sagas/OrderSaga.cs b/samples/Sample4/OpenSleigh.Samples.Sample4.Orchestrator/Sagas/OrderSaga.cs index 30d9baea..7035be4a 100644 --- a/samples/Sample4/OpenSleigh.Samples.Sample4.Orchestrator/Sagas/OrderSaga.cs +++ b/samples/Sample4/OpenSleigh.Samples.Sample4.Orchestrator/Sagas/OrderSaga.cs @@ -37,10 +37,10 @@ public async Task HandleAsync(IMessageContext context, CancellationTo this.State.OrderId = context.Message.OrderId; var startCreditCheck = ProcessCreditCheck.New(context.Message.OrderId); - await this.Bus.PublishAsync(startCreditCheck, cancellationToken); + this.Publish(startCreditCheck); var startInventoryCheck = CheckInventory.New(context.Message.OrderId); - await this.Bus.PublishAsync(startInventoryCheck, cancellationToken); + this.Publish(startInventoryCheck); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) @@ -66,7 +66,7 @@ public async Task HandleAsync(IMessageContext context, Cancel _logger.LogInformation($"shipping for order '{context.Message.OrderId}' completed!"); var message = OrderSagaCompleted.New(this.State.OrderId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); this.State.MarkAsCompleted(); } @@ -79,7 +79,7 @@ private async Task CheckStateAsync(CancellationToken cancellationToken = default return; var message = ProcessShipping.New(this.State.OrderId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } } diff --git a/samples/Sample4/OpenSleigh.Samples.Sample4.PaymentService/Sagas/CreditCheckSaga.cs b/samples/Sample4/OpenSleigh.Samples.Sample4.PaymentService/Sagas/CreditCheckSaga.cs index 0e3cd9bd..92dca8a4 100644 --- a/samples/Sample4/OpenSleigh.Samples.Sample4.PaymentService/Sagas/CreditCheckSaga.cs +++ b/samples/Sample4/OpenSleigh.Samples.Sample4.PaymentService/Sagas/CreditCheckSaga.cs @@ -28,7 +28,7 @@ public async Task HandleAsync(IMessageContext context, Cance _logger.LogInformation($"processing credit check for order '{context.Message.OrderId}'..."); var message = CrediCheckCompleted.New(context.Message.OrderId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); this.State.MarkAsCompleted(); } diff --git a/samples/Sample4/OpenSleigh.Samples.Sample4.ShippingService/Sagas/ShippingSaga.cs b/samples/Sample4/OpenSleigh.Samples.Sample4.ShippingService/Sagas/ShippingSaga.cs index 62c27fa2..06e59079 100644 --- a/samples/Sample4/OpenSleigh.Samples.Sample4.ShippingService/Sagas/ShippingSaga.cs +++ b/samples/Sample4/OpenSleigh.Samples.Sample4.ShippingService/Sagas/ShippingSaga.cs @@ -28,7 +28,7 @@ public async Task HandleAsync(IMessageContext context, Cancella _logger.LogInformation($"processing shipping for order '{context.Message.OrderId}'..."); var message = ShippingCompleted.New(context.Message.OrderId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); this.State.MarkAsCompleted(); } diff --git a/samples/Sample5/OpenSleigh.Samples.Sample5.Console/Sagas/MySaga.cs b/samples/Sample5/OpenSleigh.Samples.Sample5.Console/Sagas/MySaga.cs index 411bf9ad..c7a367e6 100644 --- a/samples/Sample5/OpenSleigh.Samples.Sample5.Console/Sagas/MySaga.cs +++ b/samples/Sample5/OpenSleigh.Samples.Sample5.Console/Sagas/MySaga.cs @@ -36,7 +36,7 @@ public async Task HandleAsync(IMessageContext context, CancellationTo throw new ApplicationException("whoops!"); var message = new MySagaCompleted(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) diff --git a/samples/Sample6/OpenSleigh.Samples.Sample6.Console/Sagas/MySaga.cs b/samples/Sample6/OpenSleigh.Samples.Sample6.Console/Sagas/MySaga.cs index 4835ab06..77ba7d58 100644 --- a/samples/Sample6/OpenSleigh.Samples.Sample6.Console/Sagas/MySaga.cs +++ b/samples/Sample6/OpenSleigh.Samples.Sample6.Console/Sagas/MySaga.cs @@ -32,7 +32,7 @@ public async Task HandleAsync(IMessageContext context, CancellationTo _logger.LogInformation($"processing saga '{context.Message.CorrelationId}'..."); var message = new MySagaCompleted(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) diff --git a/samples/Sample7/OpenSleigh.Samples.Sample7.Console/Sagas/MySaga.cs b/samples/Sample7/OpenSleigh.Samples.Sample7.Console/Sagas/MySaga.cs index 5d0bebca..1d9a9c94 100644 --- a/samples/Sample7/OpenSleigh.Samples.Sample7.Console/Sagas/MySaga.cs +++ b/samples/Sample7/OpenSleigh.Samples.Sample7.Console/Sagas/MySaga.cs @@ -46,7 +46,7 @@ public async Task HandleAsync(IMessageContext context, CancellationTo this.State.CurrentStep = MySagaState.Steps.Successful; var message = new MySagaCompleted(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public async Task CompensateAsync(ICompensationContext context, CancellationToken cancellationToken = default) @@ -56,7 +56,7 @@ public async Task CompensateAsync(ICompensationContext context, Cance this.State.CurrentStep = MySagaState.Steps.Failed; var message = new MySagaCompleted(Guid.NewGuid(), context.MessageContext.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) diff --git a/samples/Sample8/Server/Sagas/StepsSaga.cs b/samples/Sample8/Server/Sagas/StepsSaga.cs index 5a788ea9..e99148a8 100644 --- a/samples/Sample8/Server/Sagas/StepsSaga.cs +++ b/samples/Sample8/Server/Sagas/StepsSaga.cs @@ -54,9 +54,7 @@ public async Task HandleAsync(IMessageContext context, CancellationTo await SendNotification($"starting saga {context.Message.CorrelationId} with {State.TotalSteps} total steps..."); - await this.Bus.PublishAsync( - new ProcessNextStep(Guid.NewGuid(), context.Message.CorrelationId), - cancellationToken); + this.Publish(new ProcessNextStep(Guid.NewGuid(), context.Message.CorrelationId)); } public async Task HandleAsync(IMessageContext context, @@ -66,9 +64,7 @@ public async Task HandleAsync(IMessageContext context, if (State.CurrentStep > State.TotalSteps) { - await this.Bus.PublishAsync( - new SagaCompleted(Guid.NewGuid(), context.Message.CorrelationId), - cancellationToken); + this.Publish(new SagaCompleted(Guid.NewGuid(), context.Message.CorrelationId)); return; } @@ -76,9 +72,7 @@ await this.Bus.PublishAsync( await Task.Delay(250, cancellationToken); - await this.Bus.PublishAsync( - new ProcessNextStep(Guid.NewGuid(), context.Message.CorrelationId), - cancellationToken); + this.Publish(new ProcessNextStep(Guid.NewGuid(), context.Message.CorrelationId)); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) diff --git a/src/OpenSleigh.Core/BackgroundServices/OutboxBackgroundService.cs b/src/OpenSleigh.Core/BackgroundServices/OutboxBackgroundService.cs index dfd996ae..23857850 100644 --- a/src/OpenSleigh.Core/BackgroundServices/OutboxBackgroundService.cs +++ b/src/OpenSleigh.Core/BackgroundServices/OutboxBackgroundService.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using OpenSleigh.Core.Messaging; namespace OpenSleigh.Core.BackgroundServices @@ -11,24 +12,26 @@ public class OutboxBackgroundService : BackgroundService { private readonly IServiceScopeFactory _scopeFactory; private readonly OutboxProcessorOptions _options; - - public OutboxBackgroundService(IServiceScopeFactory scopeFactory, OutboxProcessorOptions options) + private readonly ILogger _logger; + + public OutboxBackgroundService(IServiceScopeFactory scopeFactory, OutboxProcessorOptions options, + ILogger logger) { _scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory)); _options = options ?? throw new ArgumentNullException(nameof(options)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - await Task.Factory.StartNew(async () => await ProcessMessagesAsync(stoppingToken), - stoppingToken, - TaskCreationOptions.LongRunning, - TaskScheduler.Current); + _logger.LogInformation("Outbox Background Service is starting..."); + + await ProcessMessagesAsync(stoppingToken); } private async Task ProcessMessagesAsync(CancellationToken stoppingToken) { - while (true) + while (!stoppingToken.IsCancellationRequested) { using (var scope = _scopeFactory.CreateScope()) { @@ -41,5 +44,12 @@ await Task.Delay(_options.Interval, stoppingToken) .ConfigureAwait(false); } } + + public override async Task StopAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("Outbox Background Service is stopping."); + + await base.StopAsync(stoppingToken); + } } } \ No newline at end of file diff --git a/src/OpenSleigh.Core/BackgroundServices/OutboxCleanerBackgroundService.cs b/src/OpenSleigh.Core/BackgroundServices/OutboxCleanerBackgroundService.cs index effa028f..ad14fd39 100644 --- a/src/OpenSleigh.Core/BackgroundServices/OutboxCleanerBackgroundService.cs +++ b/src/OpenSleigh.Core/BackgroundServices/OutboxCleanerBackgroundService.cs @@ -20,15 +20,12 @@ public OutboxCleanerBackgroundService(IServiceScopeFactory scopeFactory, OutboxC protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - await Task.Factory.StartNew(async () => await CleanupMessagesAsync(stoppingToken), - stoppingToken, - TaskCreationOptions.LongRunning, - TaskScheduler.Current); + await CleanupMessagesAsync(stoppingToken); } private async Task CleanupMessagesAsync(CancellationToken stoppingToken) { - while (true) + while (!stoppingToken.IsCancellationRequested) { using (var scope = _scopeFactory.CreateScope()) { diff --git a/src/OpenSleigh.Core/BackgroundServices/SubscribersBackgroundService.cs b/src/OpenSleigh.Core/BackgroundServices/SubscribersBackgroundService.cs index b3f91653..9501aa4c 100644 --- a/src/OpenSleigh.Core/BackgroundServices/SubscribersBackgroundService.cs +++ b/src/OpenSleigh.Core/BackgroundServices/SubscribersBackgroundService.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using OpenSleigh.Core.Messaging; namespace OpenSleigh.Core.BackgroundServices @@ -12,37 +13,40 @@ public class SubscribersBackgroundService : BackgroundService { private readonly IEnumerable _subscribers; private readonly SystemInfo _systemInfo; + private readonly ILogger _logger; - public SubscribersBackgroundService(IEnumerable subscribers, SystemInfo systemInfo) + public SubscribersBackgroundService(IEnumerable subscribers, + SystemInfo systemInfo, + ILogger logger) { _subscribers = subscribers ?? throw new ArgumentNullException(nameof(subscribers)); _systemInfo = systemInfo ?? throw new ArgumentNullException(nameof(systemInfo)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - public override async Task StartAsync(CancellationToken cancellationToken) + public override async Task StopAsync(CancellationToken cancellationToken) { if (!_systemInfo.PublishOnly) { - var tasks = _subscribers.Select(s => s.StartAsync(cancellationToken)); - - await Task.Factory.StartNew(() => Task.WhenAll(tasks), - cancellationToken, - TaskCreationOptions.LongRunning, - TaskScheduler.Current); - } + _logger.LogInformation($"stopping subscribers on client '{_systemInfo.ClientId}' ..."); + await Task.WhenAll(_subscribers.Select(s => s.StopAsync(cancellationToken))); + } - await base.StartAsync(cancellationToken); + await base.StopAsync(cancellationToken); } - public override async Task StopAsync(CancellationToken cancellationToken) + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - if (!_systemInfo.PublishOnly) - await Task.WhenAll(_subscribers.Select(s => s.StopAsync(cancellationToken))); - - await base.StopAsync(cancellationToken); - } + if (_systemInfo.PublishOnly) + { + _logger.LogInformation($"no subscribers on client '{_systemInfo.ClientId}'"); + return; + } + + _logger.LogInformation($"starting subscribers on client '{_systemInfo.ClientId}' ..."); - protected override Task ExecuteAsync(CancellationToken stoppingToken) - => Task.CompletedTask; + var tasks = _subscribers.Select(s => s.StartAsync(stoppingToken)); + await Task.WhenAll(tasks); + } } } diff --git a/src/OpenSleigh.Core/DefaultSagaFactory.cs b/src/OpenSleigh.Core/DefaultSagaFactory.cs index d513bdc2..0b3bb133 100644 --- a/src/OpenSleigh.Core/DefaultSagaFactory.cs +++ b/src/OpenSleigh.Core/DefaultSagaFactory.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using System; -using OpenSleigh.Core.Messaging; namespace OpenSleigh.Core { @@ -23,9 +22,6 @@ public TS Create(TD state) var saga = _serviceProvider.GetRequiredService(); saga.SetState(state); - - var bus = _serviceProvider.GetRequiredService(); - saga.SetBus(bus); return saga; } diff --git a/src/OpenSleigh.Core/ICompensateMessage.cs b/src/OpenSleigh.Core/ICompensateMessage.cs index 2bbebb5d..83745433 100644 --- a/src/OpenSleigh.Core/ICompensateMessage.cs +++ b/src/OpenSleigh.Core/ICompensateMessage.cs @@ -4,7 +4,7 @@ namespace OpenSleigh.Core { - public interface ICompensateMessage where TM : IMessage + public interface ICompensateMessage where TM : IMessage { Task CompensateAsync(ICompensationContext context, CancellationToken cancellationToken = default); } diff --git a/src/OpenSleigh.Core/Saga.cs b/src/OpenSleigh.Core/Saga.cs index e248c6ad..e469621e 100644 --- a/src/OpenSleigh.Core/Saga.cs +++ b/src/OpenSleigh.Core/Saga.cs @@ -9,17 +9,13 @@ public abstract class Saga : ISaga where TD : SagaState { public TD State { get; private set; } - public IMessageBus Bus { get; private set; } - + internal void SetState(TD state) { this.State = state ?? throw new ArgumentNullException(nameof(state)); } - // TODO: consider removing this. Refactor repo to store Saga (state+outbox) - internal void SetBus(IMessageBus bus) - { - this.Bus = bus ?? throw new ArgumentNullException(nameof(bus)); - } + protected void Publish(TM message) where TM : IMessage + => this.State.AddToOutbox(message); } } \ No newline at end of file diff --git a/src/OpenSleigh.Core/SagaRunner.cs b/src/OpenSleigh.Core/SagaRunner.cs index 70ca574f..d83525ab 100644 --- a/src/OpenSleigh.Core/SagaRunner.cs +++ b/src/OpenSleigh.Core/SagaRunner.cs @@ -35,7 +35,7 @@ public SagaRunner(ISagaFactory sagaFactory, public async Task RunAsync(IMessageContext messageContext, CancellationToken cancellationToken = default) where TM : IMessage { - var (state, lockId) = await GetStateAsync(messageContext, cancellationToken); + var (state, lockId) = await _sagaStateService.GetAsync(messageContext, cancellationToken); if (state.IsCompleted()) { @@ -110,22 +110,5 @@ private async Task ExecuteCompensationAsync(ICompensateMessage compensat throw; } } - - private async Task<(TD state, Guid lockId)> GetStateAsync(IMessageContext messageContext, CancellationToken cancellationToken) - where TM : IMessage - { - var policy = Policy.Retry(builder => - { - builder.WithDelay(i => TimeSpan.FromSeconds(i)) - .Handle() - .WithMaxRetries(10) - .OnException(ctx => - { - _logger.LogWarning( - $"unable to lock state for saga '{messageContext.Message.CorrelationId}': '{ctx.Exception.Message}'. Retrying..."); - }); - }); - return await policy.WrapAsync(() => _sagaStateService.GetAsync(messageContext, cancellationToken)); - } } } \ No newline at end of file diff --git a/src/OpenSleigh.Core/SagaState.cs b/src/OpenSleigh.Core/SagaState.cs index d7889f21..273d64cd 100644 --- a/src/OpenSleigh.Core/SagaState.cs +++ b/src/OpenSleigh.Core/SagaState.cs @@ -1,13 +1,18 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using OpenSleigh.Core.Messaging; +using OpenSleigh.Core.Persistence; namespace OpenSleigh.Core { //TODO: get rid of Newtonsoft.JSON dependency public abstract class SagaState { + private readonly List _outbox = new(); + [JsonProperty] //TODO: can we use an HashSet here ? private readonly Dictionary _processedMessages = new(); @@ -40,5 +45,19 @@ public bool CheckWasProcessed(TM message) where TM : IMessage public bool IsCompleted() => _isComplete; public void MarkAsCompleted() => _isComplete = true; + + internal void AddToOutbox(TM message) where TM : IMessage + { + if (message == null) + throw new ArgumentNullException(nameof(message)); + _outbox.Add(message); + } + + internal async Task PersistOutboxAsync(IOutboxRepository outboxRepository, CancellationToken cancellationToken = default) + { + foreach (var message in _outbox) + await outboxRepository.AppendAsync(message, cancellationToken); + _outbox.Clear(); + } } } \ No newline at end of file diff --git a/src/OpenSleigh.Core/SagaStateService.cs b/src/OpenSleigh.Core/SagaStateService.cs index a7f50894..76b26c20 100644 --- a/src/OpenSleigh.Core/SagaStateService.cs +++ b/src/OpenSleigh.Core/SagaStateService.cs @@ -13,11 +13,15 @@ public class SagaStateService : ISagaStateService { private readonly ISagaStateFactory _sagaStateFactory; private readonly ISagaStateRepository _sagaStateRepository; + private readonly IOutboxRepository _outboxRepository; - public SagaStateService(ISagaStateFactory sagaStateFactory, ISagaStateRepository uow) + public SagaStateService(ISagaStateFactory sagaStateFactory, + ISagaStateRepository sagaStateRepository, + IOutboxRepository outboxRepository) { _sagaStateFactory = sagaStateFactory ?? throw new ArgumentNullException(nameof(sagaStateFactory)); - _sagaStateRepository = uow ?? throw new ArgumentNullException(nameof(uow)); + _sagaStateRepository = sagaStateRepository ?? throw new ArgumentNullException(nameof(sagaStateRepository)); + _outboxRepository = outboxRepository ?? throw new ArgumentNullException(nameof(outboxRepository)); } public async Task<(TD state, Guid lockId)> GetAsync(IMessageContext messageContext, @@ -47,6 +51,7 @@ public SagaStateService(ISagaStateFactory sagaStateFactory, ISagaStateReposi public async Task SaveAsync(TD state, Guid lockId, CancellationToken cancellationToken = default) { await _sagaStateRepository.ReleaseLockAsync(state, lockId, cancellationToken); + await state.PersistOutboxAsync(_outboxRepository, cancellationToken); } } } \ No newline at end of file diff --git a/src/OpenSleigh.Persistence.Cosmos.SQL/CosmosSqlOutboxRepository.cs b/src/OpenSleigh.Persistence.Cosmos.SQL/CosmosSqlOutboxRepository.cs index 3e9de947..5192eff2 100644 --- a/src/OpenSleigh.Persistence.Cosmos.SQL/CosmosSqlOutboxRepository.cs +++ b/src/OpenSleigh.Persistence.Cosmos.SQL/CosmosSqlOutboxRepository.cs @@ -69,7 +69,10 @@ private async Task ReleaseAsyncCore(IMessage message, Guid lockId, CancellationT try { var entity = await _dbContext.OutboxMessages - .FirstOrDefaultAsync(e => e.Id == message.Id, cancellationToken) + .FirstOrDefaultAsync(e => + e.Id == message.Id && + e.LockId == lockId, + cancellationToken) .ConfigureAwait(false); if (entity is null) throw new ArgumentException($"message '{message.Id}' not found"); @@ -149,7 +152,12 @@ private async Task LockAsyncCore(IMessage message, CancellationToken cance var transaction = await _dbContext.StartTransactionAsync(cancellationToken); try { - var entity = await _dbContext.OutboxMessages.FirstOrDefaultAsync(e => e.Id == message.Id, + var expirationDate = DateTime.UtcNow - _options.LockMaxDuration; + + var entity = await _dbContext.OutboxMessages.FirstOrDefaultAsync(e => + e.Id == message.Id && + (e.LockId == null || e.LockTime > expirationDate) && + e.Status == OutboxMessage.MessageStatuses.Pending, cancellationToken: cancellationToken) .ConfigureAwait(false); if (entity is null) diff --git a/src/OpenSleigh.Persistence.Cosmos.SQL/CosmosSqlSagaStateRepository.cs b/src/OpenSleigh.Persistence.Cosmos.SQL/CosmosSqlSagaStateRepository.cs index 9e555928..9b92970e 100644 --- a/src/OpenSleigh.Persistence.Cosmos.SQL/CosmosSqlSagaStateRepository.cs +++ b/src/OpenSleigh.Persistence.Cosmos.SQL/CosmosSqlSagaStateRepository.cs @@ -33,14 +33,13 @@ public CosmosSqlSagaStateRepository(ISagaDbContext dbContext, ISerializer serial { TD resultState; var stateType = typeof(TD); - var lockId = Guid.NewGuid(); + Guid lockId; var transaction = await _dbContext.StartTransactionAsync(cancellationToken); try { var stateEntity = await _dbContext.SagaStates - .AsNoTracking() .FirstOrDefaultAsync(e => e.CorrelationId == correlationId && e.Type == stateType.FullName, cancellationToken) .ConfigureAwait(false); @@ -50,13 +49,11 @@ public CosmosSqlSagaStateRepository(ISagaDbContext dbContext, ISerializer serial resultState = newState; var serializedState = await _serializer.SerializeAsync(newState, cancellationToken); - var newEntity = new Entities.SagaState(Guid.NewGuid().ToString(), - correlationId, stateType.FullName) - { - Data = serializedState, - LockId = lockId, - LockTime = DateTime.UtcNow - }; + var newEntity = Entities.SagaState.New(correlationId, stateType.FullName); + + newEntity.Lock(serializedState); + lockId = newEntity.LockId.Value; + _dbContext.SagaStates.Add(newEntity); } else @@ -65,10 +62,10 @@ public CosmosSqlSagaStateRepository(ISagaDbContext dbContext, ISerializer serial stateEntity.LockTime.HasValue && stateEntity.LockTime.Value > DateTime.UtcNow - _options.LockMaxDuration) throw new LockException($"saga state '{correlationId}' is already locked"); - - stateEntity.LockTime = DateTime.UtcNow; - stateEntity.LockId = lockId; - + + stateEntity.RefreshLock(); + lockId = stateEntity.LockId.Value; + resultState = await _serializer.DeserializeAsync(stateEntity.Data, cancellationToken); } @@ -91,24 +88,24 @@ public Task ReleaseLockAsync(TD state, Guid lockId, if (state == null) throw new ArgumentNullException(nameof(state)); - return ReleaseLockAsyncCore(state, cancellationToken); + return ReleaseLockAsyncCore(state, lockId, cancellationToken); } - private async Task ReleaseLockAsyncCore(TD state, CancellationToken cancellationToken) where TD : SagaState + private async Task ReleaseLockAsyncCore(TD state, Guid lockId, CancellationToken cancellationToken) where TD : SagaState { var stateType = typeof(TD); var stateEntity = await _dbContext.SagaStates - .FirstOrDefaultAsync(e => e.CorrelationId == state.Id && + .FirstOrDefaultAsync(e => e.LockId == lockId && + e.CorrelationId == state.Id && e.Type == stateType.FullName, cancellationToken) .ConfigureAwait(false); if (null == stateEntity) - throw new LockException($"unable to find Saga State '{state.Id}'"); + throw new LockException($"unable to release Saga State '{state.Id}' with type '{stateType.FullName}' by lock id {lockId}"); - stateEntity.LockTime = null; - stateEntity.LockId = null; - stateEntity.Data = await _serializer.SerializeAsync(state, cancellationToken); + var newStateData = await _serializer.SerializeAsync(state, cancellationToken); + stateEntity.Release(newStateData); await _dbContext.SaveChangesAsync(cancellationToken) .ConfigureAwait(false); diff --git a/src/OpenSleigh.Persistence.Cosmos.SQL/Entities/SagaState.cs b/src/OpenSleigh.Persistence.Cosmos.SQL/Entities/SagaState.cs index 6a40db40..622a6bb8 100644 --- a/src/OpenSleigh.Persistence.Cosmos.SQL/Entities/SagaState.cs +++ b/src/OpenSleigh.Persistence.Cosmos.SQL/Entities/SagaState.cs @@ -2,11 +2,45 @@ namespace OpenSleigh.Persistence.Cosmos.SQL.Entities { - public record SagaState(string PartitionKey, Guid CorrelationId, string Type) + public class SagaState { - public byte[] Data { get; set; } = null; - public Guid? LockId { get; set; } = null; - public DateTime? LockTime { get; set; } = null; - } + private SagaState() { } + private SagaState(Guid correlationId, string type) + { + PartitionKey = correlationId.ToString(); + CorrelationId = correlationId; + Type = type; + } + + public string PartitionKey { get; init; } + public Guid CorrelationId { get; } + public string Type { get; } + + public byte[] Data { get; private set; } + public Guid? LockId { get; private set; } + public DateTime? LockTime { get; private set; } + + public void Lock(byte[] data) + { + this.Data = data; + this.LockId = Guid.NewGuid(); + this.LockTime = DateTime.UtcNow; + } + public void RefreshLock() + { + this.LockId = Guid.NewGuid(); + this.LockTime = DateTime.UtcNow; + } + + public void Release(byte[] data) + { + this.LockTime = null; + this.LockId = null; + this.Data = data; + } + + public static SagaState New(Guid correlationId, string type) + => new SagaState(correlationId, type); + } } diff --git a/src/OpenSleigh.Persistence.Cosmos.SQL/SagaDbContext.cs b/src/OpenSleigh.Persistence.Cosmos.SQL/SagaDbContext.cs index e980fc20..8650ba38 100644 --- a/src/OpenSleigh.Persistence.Cosmos.SQL/SagaDbContext.cs +++ b/src/OpenSleigh.Persistence.Cosmos.SQL/SagaDbContext.cs @@ -29,8 +29,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.ApplyConfiguration(new OutboxMessageStateEntityTypeConfiguration()); } - public async Task StartTransactionAsync(CancellationToken cancellationToken = default) - => new NullTransaction(); // https://github.com/dotnet/efcore/issues/16836 + public Task StartTransactionAsync(CancellationToken cancellationToken = default) + => Task.FromResult(new NullTransaction() as ITransaction); // https://github.com/dotnet/efcore/issues/16836 public DbSet SagaStates { get; set; } public DbSet OutboxMessages { get; set; } diff --git a/src/OpenSleigh.Persistence.InMemory/Messaging/InMemorySubscriber.cs b/src/OpenSleigh.Persistence.InMemory/Messaging/InMemorySubscriber.cs index c015ed84..83dcfe6e 100644 --- a/src/OpenSleigh.Persistence.InMemory/Messaging/InMemorySubscriber.cs +++ b/src/OpenSleigh.Persistence.InMemory/Messaging/InMemorySubscriber.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Reflection.Metadata.Ecma335; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; @@ -27,7 +25,7 @@ public InMemorySubscriber(IMessageProcessor messageProcessor, public Task StartAsync(CancellationToken cancellationToken = default) => Task.Factory.StartNew(async () => await ConsumeMessagesAsync(cancellationToken), - cancellationToken, + CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Current); diff --git a/src/OpenSleigh.Persistence.SQL/SqlOutboxRepository.cs b/src/OpenSleigh.Persistence.SQL/SqlOutboxRepository.cs index e4790d15..b5d23183 100644 --- a/src/OpenSleigh.Persistence.SQL/SqlOutboxRepository.cs +++ b/src/OpenSleigh.Persistence.SQL/SqlOutboxRepository.cs @@ -74,7 +74,10 @@ private async Task ReleaseAsyncCore(IMessage message, Guid lockId, CancellationT try { var entity = await _dbContext.OutboxMessages - .FirstOrDefaultAsync(e => e.Id == message.Id, cancellationToken) + .FirstOrDefaultAsync(e => + e.Id == message.Id && + e.LockId == lockId, + cancellationToken) .ConfigureAwait(false); if (entity is null) throw new ArgumentException($"message '{message.Id}' not found"); @@ -159,7 +162,12 @@ private async Task LockAsyncCore(IMessage message, CancellationToken cance var transaction = await _dbContext.StartTransactionAsync(cancellationToken); try { - var entity = await _dbContext.OutboxMessages.FirstOrDefaultAsync(e => e.Id == message.Id, + var expirationDate = DateTime.UtcNow - _options.LockMaxDuration; + + var entity = await _dbContext.OutboxMessages.FirstOrDefaultAsync(e => + e.Id == message.Id && + (e.LockId == null || e.LockTime > expirationDate) && + e.Status == MessageStatuses.Pending.ToString(), cancellationToken: cancellationToken) .ConfigureAwait(false); if (entity is null) diff --git a/src/OpenSleigh.Persistence.SQL/SqlSagaStateRepository.cs b/src/OpenSleigh.Persistence.SQL/SqlSagaStateRepository.cs index 85b8751c..981ec589 100644 --- a/src/OpenSleigh.Persistence.SQL/SqlSagaStateRepository.cs +++ b/src/OpenSleigh.Persistence.SQL/SqlSagaStateRepository.cs @@ -40,7 +40,6 @@ public SqlSagaStateRepository(ISagaDbContext dbContext, ISerializer serializer, try { var stateEntity = await _dbContext.SagaStates - .AsNoTracking() .FirstOrDefaultAsync(e => e.CorrelationId == correlationId && e.Type == stateType.FullName, cancellationToken) .ConfigureAwait(false); @@ -86,20 +85,21 @@ public Task ReleaseLockAsync(TD state, Guid lockId, if (state == null) throw new ArgumentNullException(nameof(state)); - return ReleaseLockAsyncCore(state, cancellationToken); + return ReleaseLockAsyncCore(state, lockId, cancellationToken); } - private async Task ReleaseLockAsyncCore(TD state, CancellationToken cancellationToken) where TD : SagaState + private async Task ReleaseLockAsyncCore(TD state, Guid lockId, CancellationToken cancellationToken) where TD : SagaState { var stateType = typeof(TD); var stateEntity = await _dbContext.SagaStates - .FirstOrDefaultAsync(e => e.CorrelationId == state.Id && + .FirstOrDefaultAsync(e => e.LockId == lockId && + e.CorrelationId == state.Id && e.Type == stateType.FullName, cancellationToken) .ConfigureAwait(false); if (null == stateEntity) - throw new LockException($"unable to find Saga State '{state.Id}'"); + throw new LockException($"unable to release Saga State '{state.Id}' with type '{stateType.FullName}' by lock id {lockId}"); stateEntity.LockTime = null; stateEntity.LockId = null; diff --git a/src/OpenSleigh.Transport.AzureServiceBus/AzureServiceBusConfiguratorExtensions.cs b/src/OpenSleigh.Transport.AzureServiceBus/AzureServiceBusConfiguratorExtensions.cs index 2843e951..25e283dc 100644 --- a/src/OpenSleigh.Transport.AzureServiceBus/AzureServiceBusConfiguratorExtensions.cs +++ b/src/OpenSleigh.Transport.AzureServiceBus/AzureServiceBusConfiguratorExtensions.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using OpenSleigh.Core.DependencyInjection; using OpenSleigh.Core.Messaging; -using Microsoft.Extensions.Azure; +using Azure.Messaging.ServiceBus; namespace OpenSleigh.Transport.AzureServiceBus { @@ -16,18 +16,13 @@ public static class AzureServiceBusConfiguratorExtensions public static IBusConfigurator UseAzureServiceBusTransport(this IBusConfigurator busConfigurator, AzureServiceBusConfiguration config, Action builderFunc = null) - { - busConfigurator.Services.AddAzureClients(builder => - { - builder.AddServiceBusClient(config.ConnectionString); - }); - + { //https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-performance-improvements?tabs=net-standard-sdk-2#reusing-factories-and-clients busConfigurator.Services .AddSingleton(config) + .AddSingleton(ctx => new ServiceBusClient(config.ConnectionString, new ServiceBusClientOptions())) .AddSingleton() - .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(); diff --git a/src/OpenSleigh.Transport.AzureServiceBus/AzureServiceBusInfrastructureCreator.cs b/src/OpenSleigh.Transport.AzureServiceBus/AzureServiceBusInfrastructureCreator.cs index 12bf3a64..911faacb 100644 --- a/src/OpenSleigh.Transport.AzureServiceBus/AzureServiceBusInfrastructureCreator.cs +++ b/src/OpenSleigh.Transport.AzureServiceBus/AzureServiceBusInfrastructureCreator.cs @@ -4,9 +4,9 @@ using Microsoft.Extensions.Hosting; using OpenSleigh.Core; using OpenSleigh.Core.Messaging; -using OpenSleigh.Transport.AzureServiceBus; +using System; -namespace OpenSleigh.Samples.Sample6.Console +namespace OpenSleigh.Transport.AzureServiceBus { internal class AzureServiceBusInfrastructureCreator : IInfrastructureCreator where TM : IMessage @@ -21,11 +21,29 @@ public async Task SetupAsync(IHost host) var azureConfig = scope.ServiceProvider.GetRequiredService(); var adminClient = new ServiceBusAdministrationClient(azureConfig.ConnectionString); - if (!(await adminClient.TopicExistsAsync(policy.TopicName))) - await adminClient.CreateTopicAsync(policy.TopicName); + try + { + if (!await adminClient.TopicExistsAsync(policy.TopicName)) + await adminClient.CreateTopicAsync(new CreateTopicOptions(policy.TopicName) + { + RequiresDuplicateDetection = true + }); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } - if (!(await adminClient.SubscriptionExistsAsync(policy.TopicName, policy.SubscriptionName))) - await adminClient.CreateSubscriptionAsync(policy.TopicName, policy.SubscriptionName); + try + { + if (!await adminClient.SubscriptionExistsAsync(policy.TopicName, policy.SubscriptionName)) + await adminClient.CreateSubscriptionAsync(policy.TopicName, policy.SubscriptionName); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } } } \ No newline at end of file diff --git a/src/OpenSleigh.Transport.AzureServiceBus/AzureServiceBusSagaConfiguratorExtensions.cs b/src/OpenSleigh.Transport.AzureServiceBus/AzureServiceBusSagaConfiguratorExtensions.cs index 8d4ef1e1..0056bc82 100644 --- a/src/OpenSleigh.Transport.AzureServiceBus/AzureServiceBusSagaConfiguratorExtensions.cs +++ b/src/OpenSleigh.Transport.AzureServiceBus/AzureServiceBusSagaConfiguratorExtensions.cs @@ -3,7 +3,6 @@ using OpenSleigh.Core; using OpenSleigh.Core.DependencyInjection; using OpenSleigh.Core.Utils; -using OpenSleigh.Samples.Sample6.Console; namespace OpenSleigh.Transport.AzureServiceBus { diff --git a/src/OpenSleigh.Transport.AzureServiceBus/IServiceBusProcessorFactory.cs b/src/OpenSleigh.Transport.AzureServiceBus/IServiceBusProcessorFactory.cs deleted file mode 100644 index be0599cb..00000000 --- a/src/OpenSleigh.Transport.AzureServiceBus/IServiceBusProcessorFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Azure.Messaging.ServiceBus; -using OpenSleigh.Core.Messaging; - -namespace OpenSleigh.Transport.AzureServiceBus -{ - internal interface IServiceBusProcessorFactory - { - ServiceBusProcessor Create() where TM : IMessage; - } -} \ No newline at end of file diff --git a/src/OpenSleigh.Transport.AzureServiceBus/ServiceBusProcessorFactory.cs b/src/OpenSleigh.Transport.AzureServiceBus/ServiceBusProcessorFactory.cs deleted file mode 100644 index f6c605b0..00000000 --- a/src/OpenSleigh.Transport.AzureServiceBus/ServiceBusProcessorFactory.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Threading.Tasks; -using Azure.Messaging.ServiceBus; -using OpenSleigh.Core.Messaging; - -namespace OpenSleigh.Transport.AzureServiceBus -{ - internal class ServiceBusProcessorFactory : IAsyncDisposable, IServiceBusProcessorFactory - { - private readonly IQueueReferenceFactory _queueReferenceFactory; - private readonly ServiceBusClient _serviceBusClient; - private readonly ConcurrentDictionary _processors = new(); - - public ServiceBusProcessorFactory(IQueueReferenceFactory queueReferenceFactory, ServiceBusClient serviceBusClient) - { - _queueReferenceFactory = queueReferenceFactory ?? throw new ArgumentNullException(nameof(queueReferenceFactory)); - _serviceBusClient = serviceBusClient ?? throw new ArgumentNullException(nameof(serviceBusClient)); - } - - public ServiceBusProcessor Create() where TM : IMessage - { - var references = _queueReferenceFactory.Create(); - - var processor = _processors.GetOrAdd(references, _ => _serviceBusClient.CreateProcessor(references.TopicName, references.SubscriptionName)); - if (processor is null || processor.IsClosed) - processor = _processors[references] = _serviceBusClient.CreateProcessor(references.TopicName, references.SubscriptionName); - - return processor; - } - - public async ValueTask DisposeAsync() - { - foreach (var sender in _processors.Values) - await sender.CloseAsync().ConfigureAwait(false); - _processors.Clear(); - } - } -} \ No newline at end of file diff --git a/src/OpenSleigh.Transport.AzureServiceBus/ServiceBusPublisher.cs b/src/OpenSleigh.Transport.AzureServiceBus/ServiceBusPublisher.cs index 4044b1e3..c3de43d0 100644 --- a/src/OpenSleigh.Transport.AzureServiceBus/ServiceBusPublisher.cs +++ b/src/OpenSleigh.Transport.AzureServiceBus/ServiceBusPublisher.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Azure.Messaging.ServiceBus; using Microsoft.Extensions.Logging; +using OpenSleigh.Core; using OpenSleigh.Core.Messaging; using OpenSleigh.Core.Utils; @@ -13,14 +14,16 @@ internal class ServiceBusPublisher : IPublisher private readonly IServiceBusSenderFactory _senderFactory; private readonly ISerializer _serializer; private readonly ILogger _logger; + private readonly SystemInfo _systemInfo; - public ServiceBusPublisher(IServiceBusSenderFactory senderFactory, - ISerializer serializer, - ILogger logger) + public ServiceBusPublisher(IServiceBusSenderFactory senderFactory, + ISerializer serializer, + ILogger logger, SystemInfo systemInfo) { _senderFactory = senderFactory ?? throw new ArgumentNullException(nameof(senderFactory)); _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _systemInfo = systemInfo ?? throw new ArgumentNullException(nameof(systemInfo)); } public Task PublishAsync(IMessage message, CancellationToken cancellationToken = default) @@ -34,8 +37,7 @@ public Task PublishAsync(IMessage message, CancellationToken cancellationToken = private async Task PublishAsyncCore(IMessage message, CancellationToken cancellationToken) { ServiceBusSender sender = _senderFactory.Create((dynamic) message); - _logger.LogInformation( - $"publishing message '{message.Id}' to {sender.FullyQualifiedNamespace}/{sender.EntityPath}"); + _logger.LogInformation($"client '{_systemInfo.ClientId}' publishing message '{message.Id}' to {sender.FullyQualifiedNamespace}/{sender.EntityPath}"); var serializedMessage = await _serializer.SerializeAsync(message, cancellationToken); var busMessage = new ServiceBusMessage(serializedMessage) diff --git a/src/OpenSleigh.Transport.AzureServiceBus/ServiceBusSubscriber.cs b/src/OpenSleigh.Transport.AzureServiceBus/ServiceBusSubscriber.cs index b43df06e..db4d343a 100644 --- a/src/OpenSleigh.Transport.AzureServiceBus/ServiceBusSubscriber.cs +++ b/src/OpenSleigh.Transport.AzureServiceBus/ServiceBusSubscriber.cs @@ -1,5 +1,6 @@ using Azure.Messaging.ServiceBus; using Microsoft.Extensions.Logging; +using OpenSleigh.Core; using OpenSleigh.Core.Messaging; using System; using System.Threading; @@ -7,33 +8,35 @@ namespace OpenSleigh.Transport.AzureServiceBus { - internal sealed class ServiceBusSubscriber : ISubscriber, IDisposable + internal sealed class ServiceBusSubscriber : ISubscriber, IAsyncDisposable where TM : IMessage { - private ServiceBusProcessor _processor; private readonly IMessageProcessor _messageProcessor; - private readonly IMessageParser _messageParser; + private readonly IMessageParser _messageParser; private readonly ILogger> _logger; - - public ServiceBusSubscriber(IServiceBusProcessorFactory processorFactory, + private readonly SystemInfo _systemInfo; + private ServiceBusProcessor _processor; + + public ServiceBusSubscriber(IQueueReferenceFactory queueReferenceFactory, + ServiceBusClient serviceBusClient, IMessageParser messageParser, - IMessageProcessor messageProcessor, - ILogger> logger) - { - if (processorFactory == null) - throw new ArgumentNullException(nameof(processorFactory)); + IMessageProcessor messageProcessor, + ILogger> logger, SystemInfo systemInfo) + { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _messageParser = messageParser ?? throw new ArgumentNullException(nameof(messageParser)); _messageProcessor = messageProcessor ?? throw new ArgumentNullException(nameof(messageProcessor)); + _systemInfo = systemInfo ?? throw new ArgumentNullException(nameof(systemInfo)); - _processor = processorFactory.Create(); + var references = queueReferenceFactory.Create(); + _processor = serviceBusClient.CreateProcessor(references.TopicName, references.SubscriptionName); _processor.ProcessMessageAsync += MessageHandler; - _processor.ProcessErrorAsync += ErrorHandler; + _processor.ProcessErrorAsync += ProcessErrorAsync; } - private Task ErrorHandler(ProcessErrorEventArgs args) + private Task ProcessErrorAsync(ProcessErrorEventArgs arg) { - _logger.LogError(args.Exception, $"an error has occurred while processing messages: {args.Exception.Message}"); + _logger.LogError(arg.Exception, $"an exception has occurred while processing a message on client '{_systemInfo.ClientId}'"); return Task.CompletedTask; } @@ -41,6 +44,8 @@ private async Task MessageHandler(ProcessMessageEventArgs args) { try { + _logger.LogInformation($"client '{_systemInfo.ClientId}' received message '{args.Message.MessageId}'. Processing..."); + var message = _messageParser.Resolve(args.Message.Body); await _messageProcessor.ProcessAsync((dynamic)message, args.CancellationToken); @@ -57,15 +62,31 @@ private async Task MessageHandler(ProcessMessageEventArgs args) } } - public async Task StartAsync(CancellationToken cancellationToken = default) - => await _processor.StartProcessingAsync(cancellationToken).ConfigureAwait(false); + public async Task StartAsync(CancellationToken cancellationToken = default) + { + await _processor.StartProcessingAsync(cancellationToken).ConfigureAwait(false); + _logger.LogInformation($"subscriber started on client '{_systemInfo.ClientId}' for '{_processor.EntityPath}'"); + } - public async Task StopAsync(CancellationToken cancellationToken = default) - => await _processor.StopProcessingAsync(cancellationToken).ConfigureAwait(false); + public Task StopAsync(CancellationToken cancellationToken = default) + => Task.CompletedTask; - public void Dispose() +#if !DEBUG + public async ValueTask DisposeAsync() { + // calling DisposeAsync() on each processor might take a long time to complete (~60sec) due to + // apparent limitations of the underlying AMQP library. + // more details here: https://github.com/Azure/azure-sdk-for-net/issues/19306 + // Therefore we do it only when in Release mode. Debug mode is used when running the tests suite. + await _processor.StartProcessingAsync(); + _processor.ProcessMessageAsync -= MessageHandler; + _processor.ProcessErrorAsync -= ProcessErrorAsync; + await _processor.DisposeAsync(); _processor = null; } +#else + public ValueTask DisposeAsync() => ValueTask.CompletedTask; +#endif + } } \ No newline at end of file diff --git a/src/OpenSleigh.Transport.Kafka/KafkaConfiguratorExtensions.cs b/src/OpenSleigh.Transport.Kafka/KafkaConfiguratorExtensions.cs index 7c63255a..f9d5ff69 100644 --- a/src/OpenSleigh.Transport.Kafka/KafkaConfiguratorExtensions.cs +++ b/src/OpenSleigh.Transport.Kafka/KafkaConfiguratorExtensions.cs @@ -25,6 +25,13 @@ public static IBusConfigurator UseKafkaTransport(this IBusConfigurator busConfig return new QueueReferenceFactory(ctx, config.DefaultQueueReferenceCreator); }); + busConfigurator.Services.AddSingleton(new AdminClientConfig() { BootstrapServers = config.ConnectionString }); + busConfigurator.Services.AddSingleton(ctx => + { + var config = ctx.GetRequiredService(); + return new AdminClientBuilder(config); + }); + busConfigurator.Services.AddSingleton(new ProducerConfig() { BootstrapServers = config.ConnectionString } ); busConfigurator.Services.AddSingleton(ctx => { diff --git a/src/OpenSleigh.Transport.Kafka/KafkaInfrastructureCreator.cs b/src/OpenSleigh.Transport.Kafka/KafkaInfrastructureCreator.cs new file mode 100644 index 00000000..a3bd7066 --- /dev/null +++ b/src/OpenSleigh.Transport.Kafka/KafkaInfrastructureCreator.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using Confluent.Kafka; +using Confluent.Kafka.Admin; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using OpenSleigh.Core; +using OpenSleigh.Core.Messaging; + +namespace OpenSleigh.Transport.Kafka +{ + internal class KafkaInfrastructureCreator : IInfrastructureCreator + where TM : IMessage + { + private readonly ILogger> _logger; + + public KafkaInfrastructureCreator(ILogger> logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task SetupAsync(IHost host) + { + using var scope = host.Services.CreateScope(); + var builder = scope.ServiceProvider.GetRequiredService(); + var queueReferenceFactory = scope.ServiceProvider.GetRequiredService(); + var queueRef = queueReferenceFactory.Create(); + + using var adminClient = builder.Build(); + + _logger.LogInformation("Setting up Kafka topic {Topic} ...", queueRef.TopicName); + + try + { + await adminClient.CreateTopicsAsync(new[] { + new TopicSpecification { Name = queueRef.TopicName, ReplicationFactor = 1, NumPartitions = 1 }, + new TopicSpecification { Name = queueRef.DeadLetterTopicName, ReplicationFactor = 1, NumPartitions = 1 } + }); + } + catch (Exception e) + { + _logger.LogError(e, "An error occured creating topic {Topic}: {Error}", + queueRef.TopicName, e.Message); + } + } + } +} \ No newline at end of file diff --git a/src/OpenSleigh.Transport.Kafka/KafkaPublisher.cs b/src/OpenSleigh.Transport.Kafka/KafkaPublisher.cs index 588227c6..6463ef18 100644 --- a/src/OpenSleigh.Transport.Kafka/KafkaPublisher.cs +++ b/src/OpenSleigh.Transport.Kafka/KafkaPublisher.cs @@ -9,7 +9,7 @@ namespace OpenSleigh.Transport.Kafka public class KafkaPublisher : IPublisher { private readonly IKafkaPublisherExecutor _executor; - private readonly IQueueReferenceFactory _queueReferenceFactory; + private readonly IQueueReferenceFactory _queueReferenceFactory; public KafkaPublisher(IKafkaPublisherExecutor executor, IQueueReferenceFactory queueReferenceFactory) { diff --git a/src/OpenSleigh.Transport.Kafka/KafkaPublisherExecutor.cs b/src/OpenSleigh.Transport.Kafka/KafkaPublisherExecutor.cs index 73807318..6f8edde7 100644 --- a/src/OpenSleigh.Transport.Kafka/KafkaPublisherExecutor.cs +++ b/src/OpenSleigh.Transport.Kafka/KafkaPublisherExecutor.cs @@ -1,26 +1,28 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; using Confluent.Kafka; +using Microsoft.Extensions.Logging; using OpenSleigh.Core.Messaging; using OpenSleigh.Core.Utils; namespace OpenSleigh.Transport.Kafka { - internal class KafkaPublisherExecutor : IKafkaPublisherExecutor + public class KafkaPublisherExecutor : IKafkaPublisherExecutor { private readonly IProducer _producer; private readonly ISerializer _serializer; + private readonly ILogger _logger; - public KafkaPublisherExecutor(IProducer producer, ISerializer serializer) + public KafkaPublisherExecutor(IProducer producer, ISerializer serializer, ILogger logger) { _producer = producer ?? throw new ArgumentNullException(nameof(producer)); _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } - + public Task> PublishAsync(IMessage message, string topic, IEnumerable
additionalHeaders = null, @@ -31,6 +33,9 @@ public Task> PublishAsync(IMessage message, if (string.IsNullOrWhiteSpace(topic)) throw new ArgumentNullException(nameof(topic), "Value cannot be null or whitespace."); + _logger.LogInformation("pushing message '{MessageId}' to topic '{Topic}' ...", + message.Id, topic); + return PublishAsyncCore(message, topic, additionalHeaders, cancellationToken); } diff --git a/src/OpenSleigh.Transport.Kafka/KafkaSagaConfiguratorExtensions.cs b/src/OpenSleigh.Transport.Kafka/KafkaSagaConfiguratorExtensions.cs index 1babc691..e764d393 100644 --- a/src/OpenSleigh.Transport.Kafka/KafkaSagaConfiguratorExtensions.cs +++ b/src/OpenSleigh.Transport.Kafka/KafkaSagaConfiguratorExtensions.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.DependencyInjection; using OpenSleigh.Core; using OpenSleigh.Core.DependencyInjection; using OpenSleigh.Core.Utils; @@ -14,8 +15,12 @@ public static ISagaConfigurator UseKafkaTransport(this ISagaConf { var messageTypes = SagaUtils.GetHandledMessageTypes(); foreach (var messageType in messageTypes) + { sagaConfigurator.Services.AddBusSubscriber( typeof(KafkaSubscriber<>).MakeGenericType(messageType)); + sagaConfigurator.Services.AddSingleton(typeof(IInfrastructureCreator), + typeof(KafkaInfrastructureCreator<>).MakeGenericType(messageType)); + } return sagaConfigurator; } diff --git a/src/OpenSleigh.Transport.RabbitMQ/RabbitSubscriber.cs b/src/OpenSleigh.Transport.RabbitMQ/RabbitSubscriber.cs index f10d2a8e..5bada337 100644 --- a/src/OpenSleigh.Transport.RabbitMQ/RabbitSubscriber.cs +++ b/src/OpenSleigh.Transport.RabbitMQ/RabbitSubscriber.cs @@ -119,7 +119,7 @@ private async Task OnMessageReceivedAsync(object sender, BasicDeliverEventArgs e return; } - _logger.LogDebug("received message '{MessageId}' from Exchange '{ExchangeName}', Queue '{QueueName}'. Processing...", + _logger.LogInformation("received message '{MessageId}' from Exchange '{ExchangeName}', Queue '{QueueName}'. Processing...", message.Id, _queueReferences.ExchangeName, _queueReferences.QueueName); try diff --git a/tests/OpenSleigh.Core.Tests/E2E/EventBroadcastingScenario.cs b/tests/OpenSleigh.Core.Tests/E2E/EventBroadcastingScenario.cs index 8a75ce62..3d92ae4b 100644 --- a/tests/OpenSleigh.Core.Tests/E2E/EventBroadcastingScenario.cs +++ b/tests/OpenSleigh.Core.Tests/E2E/EventBroadcastingScenario.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -13,46 +15,66 @@ namespace OpenSleigh.Core.Tests.E2E { public abstract class EventBroadcastingScenario : E2ETestsBase { - [Fact] - public async Task run_event_broadcasting_scenario() + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + public async Task run_event_broadcasting_scenario(int hostsCount) { - var hostBuilder = CreateHostBuilder(); - var message = new DummyEvent(Guid.NewGuid(), Guid.NewGuid()); var tokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(1)); var callsCount = 0; var expectedCount = 2; - - Action onMessage = msg => + + Action onMessage = async msg => { callsCount++; - if(callsCount >= expectedCount) - tokenSource.Cancel(); + if (callsCount < expectedCount) + return; + + tokenSource.CancelAfter(TimeSpan.FromSeconds(10)); }; - hostBuilder.ConfigureServices((ctx, services) => + var consumerHostsTasks = Enumerable.Range(1, hostsCount - 1) + .Select(async i => + { + var host = await SetupHost(onMessage); + await host.StartAsync(tokenSource.Token); + }); + + var producerHostTask = Task.Run(async () => { - services.AddSingleton(onMessage); - }); + var host = await SetupHost(onMessage); - var host = hostBuilder.Build(); + await host.StartAsync(tokenSource.Token); - await host.SetupInfrastructureAsync(); - - using var scope = host.Services.CreateScope(); - var bus = scope.ServiceProvider.GetRequiredService(); + using var scope = host.Services.CreateScope(); + var bus = scope.ServiceProvider.GetRequiredService(); + await bus.PublishAsync(message, tokenSource.Token); + }); - await Task.WhenAll(new[] + var tasks = new List(consumerHostsTasks) { - host.RunAsync(token: tokenSource.Token), - bus.PublishAsync(message, tokenSource.Token) - }); + producerHostTask + }; + + while (!tokenSource.IsCancellationRequested) + await Task.Delay(100); callsCount.Should().Be(expectedCount); } - + + private async Task SetupHost(Action onMessage) + { + var hostBuilder = CreateHostBuilder(); + hostBuilder.ConfigureServices((ctx, services) => { services.AddSingleton(onMessage); }); + var host = hostBuilder.Build(); + await host.SetupInfrastructureAsync(); + return host; + } + protected override void AddSagas(IBusConfigurator cfg) { AddSaga(cfg, msg => new EventSagaState1(msg.CorrelationId)); diff --git a/tests/OpenSleigh.Core.Tests/E2E/ParentChildScenario.cs b/tests/OpenSleigh.Core.Tests/E2E/ParentChildScenario.cs index 03dc873f..e922cdaa 100644 --- a/tests/OpenSleigh.Core.Tests/E2E/ParentChildScenario.cs +++ b/tests/OpenSleigh.Core.Tests/E2E/ParentChildScenario.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -13,39 +15,65 @@ namespace OpenSleigh.Core.Tests.E2E { public abstract class ParentChildScenario : E2ETestsBase { - [Fact] - public async Task run_parent_child_scenario() + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + public async Task run_parent_child_scenario(int hostsCount) { - var hostBuilder = CreateHostBuilder(); - var message = new StartParentSaga(Guid.NewGuid(), Guid.NewGuid()); - var received = false; + var receivedCount = 0; var tokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(2)); - + Action onMessage = msg => - { - tokenSource.Cancel(); + { + receivedCount++; + tokenSource.CancelAfter(TimeSpan.FromSeconds(10)); + msg.CorrelationId.Should().Be(message.CorrelationId); - received = true; }; - hostBuilder.ConfigureServices((ctx, services) => { services.AddSingleton(onMessage); }); + var consumerHostsTasks = Enumerable.Range(1, hostsCount - 1) + .Select(async i => + { + var host = await SetupHost(onMessage); + await host.StartAsync(tokenSource.Token); + }); - var host = hostBuilder.Build(); + var producerHostTask = Task.Run(async () => + { + var host = await SetupHost(onMessage); - await host.SetupInfrastructureAsync(); + await host.StartAsync(tokenSource.Token); - using var scope = host.Services.CreateScope(); - var bus = scope.ServiceProvider.GetRequiredService(); + using var scope = host.Services.CreateScope(); + var bus = scope.ServiceProvider.GetRequiredService(); + await bus.PublishAsync(message, tokenSource.Token); + }); - await Task.WhenAll(new[] + var tasks = new List(consumerHostsTasks) { - host.RunAsync(token: tokenSource.Token), - bus.PublishAsync(message, tokenSource.Token) - }); + producerHostTask + }; + + while (!tokenSource.IsCancellationRequested) + await Task.Delay(10); - received.Should().BeTrue(); + receivedCount.Should().Be(1); + } + + private async Task SetupHost(Action onMessage) + { + var hostBuilder = CreateHostBuilder(); + hostBuilder.ConfigureServices((ctx, services) => + { + services.AddSingleton(onMessage); + }); + var host = hostBuilder.Build(); + + await host.SetupInfrastructureAsync(); + return host; } protected override void AddSagas(IBusConfigurator cfg) diff --git a/tests/OpenSleigh.Core.Tests/E2E/SimpleSagaScenario.cs b/tests/OpenSleigh.Core.Tests/E2E/SimpleSagaScenario.cs index f88384af..9926c1c4 100644 --- a/tests/OpenSleigh.Core.Tests/E2E/SimpleSagaScenario.cs +++ b/tests/OpenSleigh.Core.Tests/E2E/SimpleSagaScenario.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -13,47 +15,83 @@ namespace OpenSleigh.Core.Tests.E2E { public abstract class SimpleSagaScenario : E2ETestsBase { - [Fact] - public async Task run_single_message_scenario() + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + public async Task run_single_message_scenario(int hostsCount) { - var hostBuilder = CreateHostBuilder(); - var message = new StartSimpleSaga(Guid.NewGuid(), Guid.NewGuid()); - - var received = false; + + var receivedCount = 0; var tokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(1)); - + Action onMessage = msg => { - tokenSource.Cancel(); - + receivedCount++; + tokenSource.CancelAfter(TimeSpan.FromSeconds(10)); + msg.Id.Should().Be(message.Id); msg.CorrelationId.Should().Be(message.CorrelationId); - - received = true; }; - hostBuilder.ConfigureServices((ctx, services) => + var consumerHostsTasks = Enumerable.Range(1, hostsCount - 1) + .Select(async i => + { + try + { + var host = await SetupHost(onMessage); + await host.StartAsync(tokenSource.Token); + } + catch (Exception ex) + { + + throw; + } + + }); + + var producerHostTask = Task.Run(async () => { - services.AddSingleton(onMessage); - }); + try + { + var host = await SetupHost(onMessage); - var host = hostBuilder.Build(); + await host.StartAsync(tokenSource.Token); - using var scope = host.Services.CreateScope(); - var bus = scope.ServiceProvider.GetRequiredService(); + using var scope = host.Services.CreateScope(); + var bus = scope.ServiceProvider.GetRequiredService(); + await bus.PublishAsync(message, tokenSource.Token); + } + catch (Exception ex) + { - await host.SetupInfrastructureAsync(); + throw ; + } + + }); - await Task.WhenAll(new[] + var tasks = new List(consumerHostsTasks) { - host.RunAsync(token: tokenSource.Token), - bus.PublishAsync(message, tokenSource.Token) - }); + producerHostTask + }; - received.Should().BeTrue(); + while (!tokenSource.IsCancellationRequested) + await Task.Delay(10); + + receivedCount.Should().Be(1); } - + + private async Task SetupHost(Action onMessage) + { + var hostBuilder = CreateHostBuilder(); + hostBuilder.ConfigureServices((ctx, services) => { services.AddSingleton(onMessage); }); + var host = hostBuilder.Build(); + + await host.SetupInfrastructureAsync(); + return host; + } + protected override void AddSagas(IBusConfigurator cfg) { AddSaga(cfg, msg => new SimpleSagaState(msg.CorrelationId)); diff --git a/tests/OpenSleigh.Core.Tests/Sagas/ChildSaga.cs b/tests/OpenSleigh.Core.Tests/Sagas/ChildSaga.cs index 3af658e3..a39c7752 100644 --- a/tests/OpenSleigh.Core.Tests/Sagas/ChildSaga.cs +++ b/tests/OpenSleigh.Core.Tests/Sagas/ChildSaga.cs @@ -25,7 +25,7 @@ public class ChildSaga : public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) { var message = new ProcessChildSaga(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) @@ -33,7 +33,7 @@ public async Task HandleAsync(IMessageContext context, Cancell this.State.MarkAsCompleted(); var completedEvent = new ChildSagaCompleted(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(completedEvent, cancellationToken); + this.Publish(completedEvent); } } } \ No newline at end of file diff --git a/tests/OpenSleigh.Core.Tests/Sagas/ParentSaga .cs b/tests/OpenSleigh.Core.Tests/Sagas/ParentSaga.cs similarity index 84% rename from tests/OpenSleigh.Core.Tests/Sagas/ParentSaga .cs rename to tests/OpenSleigh.Core.Tests/Sagas/ParentSaga.cs index cbc9a84a..ba6d893d 100644 --- a/tests/OpenSleigh.Core.Tests/Sagas/ParentSaga .cs +++ b/tests/OpenSleigh.Core.Tests/Sagas/ParentSaga.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using OpenSleigh.Core.Messaging; namespace OpenSleigh.Core.Tests.Sagas @@ -23,34 +24,37 @@ public class ParentSaga : IHandleMessage, IHandleMessage { - private readonly Action _onCompleted; - + private readonly Action _onCompleted; + private readonly ILogger _logger; - public ParentSaga(Action onCompleted) + public ParentSaga(Action onCompleted, ILogger logger) { _onCompleted = onCompleted ?? throw new ArgumentNullException(nameof(onCompleted)); + _logger = logger; } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) { var message = new ProcessParentSaga(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) { var message = new StartChildSaga(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public async Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) { var message = new ParentSagaCompleted(Guid.NewGuid(), context.Message.CorrelationId); - await this.Bus.PublishAsync(message, cancellationToken); + this.Publish(message); } public Task HandleAsync(IMessageContext context, CancellationToken cancellationToken = default) { + _logger.LogInformation($"completing Parent Saga '{context.Message.CorrelationId}'"); + this.State.MarkAsCompleted(); _onCompleted?.Invoke(context.Message); diff --git a/tests/OpenSleigh.Core.Tests/Unit/BackgroundServices/OutboxBackgroundServiceTests.cs b/tests/OpenSleigh.Core.Tests/Unit/BackgroundServices/OutboxBackgroundServiceTests.cs index 8ea5953a..dd1e9d10 100644 --- a/tests/OpenSleigh.Core.Tests/Unit/BackgroundServices/OutboxBackgroundServiceTests.cs +++ b/tests/OpenSleigh.Core.Tests/Unit/BackgroundServices/OutboxBackgroundServiceTests.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using NSubstitute; using OpenSleigh.Core.BackgroundServices; using OpenSleigh.Core.Messaging; @@ -16,8 +17,10 @@ public void ctor_should_throw_if_input_null() { var options = OutboxProcessorOptions.Default; var scopeFactory = NSubstitute.Substitute.For(); - Assert.Throws(() => new OutboxBackgroundService(null, options)); - Assert.Throws(() => new OutboxBackgroundService(scopeFactory, null)); + var logger = NSubstitute.Substitute.For>(); + Assert.Throws(() => new OutboxBackgroundService(null, options, logger)); + Assert.Throws(() => new OutboxBackgroundService(scopeFactory, null, logger)); + Assert.Throws(() => new OutboxBackgroundService(scopeFactory, options, null)); } [Fact] @@ -34,7 +37,9 @@ public async Task StartAsync_should_process_pending_messages() var factory = NSubstitute.Substitute.For(); factory.CreateScope().Returns(scope); - var sut = new OutboxBackgroundService(factory, OutboxProcessorOptions.Default); + var logger = NSubstitute.Substitute.For>(); + + var sut = new OutboxBackgroundService(factory, OutboxProcessorOptions.Default, logger); var tokenSource = new CancellationTokenSource(); await sut.StartAsync(tokenSource.Token); diff --git a/tests/OpenSleigh.Core.Tests/Unit/BackgroundServices/SubscribersBackgroundServiceTests.cs b/tests/OpenSleigh.Core.Tests/Unit/BackgroundServices/SubscribersBackgroundServiceTests.cs index bfbaad68..5da77577 100644 --- a/tests/OpenSleigh.Core.Tests/Unit/BackgroundServices/SubscribersBackgroundServiceTests.cs +++ b/tests/OpenSleigh.Core.Tests/Unit/BackgroundServices/SubscribersBackgroundServiceTests.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using NSubstitute; using OpenSleigh.Core.BackgroundServices; using OpenSleigh.Core.Messaging; @@ -18,8 +19,10 @@ public void ctor_should_throw_if_input_null() { NSubstitute.Substitute.For(), }; - Assert.Throws(() => new SubscribersBackgroundService(null, sysInfo)); - Assert.Throws(() => new SubscribersBackgroundService(subscribers, null)); + var logger = NSubstitute.Substitute.For>(); + Assert.Throws(() => new SubscribersBackgroundService(null, sysInfo, logger)); + Assert.Throws(() => new SubscribersBackgroundService(subscribers, null, logger)); + Assert.Throws(() => new SubscribersBackgroundService(subscribers, sysInfo, null)); } [Fact] @@ -33,7 +36,9 @@ public async Task StartAsync_should_start_subscribers() }; var sysInfo = SystemInfo.New(); - var sut = new SubscribersBackgroundService(subscribers, sysInfo); + var logger = NSubstitute.Substitute.For>(); + + var sut = new SubscribersBackgroundService(subscribers, sysInfo, logger); var tokenSource = new CancellationTokenSource(); await sut.StartAsync(tokenSource.Token); @@ -54,8 +59,10 @@ public async Task StartAsync_should_not_start_subscribers_when_publish_only() }; var sysInfo = SystemInfo.New(); sysInfo.PublishOnly = true; - - var sut = new SubscribersBackgroundService(subscribers, sysInfo); + + var logger = NSubstitute.Substitute.For>(); + + var sut = new SubscribersBackgroundService(subscribers, sysInfo, logger); var tokenSource = new CancellationTokenSource(); await sut.StartAsync(tokenSource.Token); diff --git a/tests/OpenSleigh.Core.Tests/Unit/DefaultSagaFactoryTests.cs b/tests/OpenSleigh.Core.Tests/Unit/DefaultSagaFactoryTests.cs index 539b3733..ce93bc6d 100644 --- a/tests/OpenSleigh.Core.Tests/Unit/DefaultSagaFactoryTests.cs +++ b/tests/OpenSleigh.Core.Tests/Unit/DefaultSagaFactoryTests.cs @@ -51,7 +51,6 @@ public void Create_should_create_valid_instance() var saga = sut.Create(state); saga.Should().NotBeNull(); saga.State.Should().Be(state); - saga.Bus.Should().Be(bus); } } } diff --git a/tests/OpenSleigh.Core.Tests/Unit/ExceptionPolicies/DefaultSagaPolicyFactoryTests.cs b/tests/OpenSleigh.Core.Tests/Unit/ExceptionPolicies/DefaultSagaPolicyFactoryTests.cs index 66da8710..053ad65a 100644 --- a/tests/OpenSleigh.Core.Tests/Unit/ExceptionPolicies/DefaultSagaPolicyFactoryTests.cs +++ b/tests/OpenSleigh.Core.Tests/Unit/ExceptionPolicies/DefaultSagaPolicyFactoryTests.cs @@ -1,7 +1,6 @@ using System; using FluentAssertions; using NSubstitute; -using NSubstitute.ReturnsExtensions; using OpenSleigh.Core.ExceptionPolicies; using OpenSleigh.Core.Tests.Sagas; using Xunit; diff --git a/tests/OpenSleigh.Core.Tests/Unit/ExceptionPolicies/ExceptionFiltersTests.cs b/tests/OpenSleigh.Core.Tests/Unit/ExceptionPolicies/ExceptionFiltersTests.cs index 049d8e0d..3f7d49a0 100644 --- a/tests/OpenSleigh.Core.Tests/Unit/ExceptionPolicies/ExceptionFiltersTests.cs +++ b/tests/OpenSleigh.Core.Tests/Unit/ExceptionPolicies/ExceptionFiltersTests.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using FluentAssertions; using OpenSleigh.Core.ExceptionPolicies; using Xunit; diff --git a/tests/OpenSleigh.Core.Tests/Unit/SagaRunnerTests.cs b/tests/OpenSleigh.Core.Tests/Unit/SagaRunnerTests.cs index 87e2b060..ef89eaff 100644 --- a/tests/OpenSleigh.Core.Tests/Unit/SagaRunnerTests.cs +++ b/tests/OpenSleigh.Core.Tests/Unit/SagaRunnerTests.cs @@ -31,52 +31,6 @@ public void ctor_should_throw_when_arguments_null(){ Assert.Throws(() => new SagaRunner(sagaFactory, sagaStateService, transactionManager, policyFactory, null)); } - [Fact] - public async Task RunAsync_should_retry_if_saga_state_locked() - { - var message = StartDummySaga.New(); - var messageContext = NSubstitute.Substitute.For>(); - messageContext.Message.Returns(message); - - var sagaStateService = NSubstitute.Substitute.For>(); - - var state = new DummySagaState(message.CorrelationId); - - var firstCall = true; - sagaStateService.When(s => s.GetAsync(messageContext, Arg.Any())) - .Do(_ => - { - if (firstCall) - { - firstCall = false; - throw new LockException("lorem"); - } - }); - sagaStateService.GetAsync(messageContext, Arg.Any()) - .Returns((state, Guid.NewGuid())); - - var saga = NSubstitute.Substitute.ForPartsOf(); - saga.SetBus(NSubstitute.Substitute.For()); - saga.When(s => s.HandleAsync(Arg.Any>(), Arg.Any())) - .DoNotCallBase(); - - var sagaFactory = NSubstitute.Substitute.For>(); - sagaFactory.Create(state) - .Returns(saga); - - var logger = NSubstitute.Substitute.For>>(); - - var transactionManager = NSubstitute.Substitute.For(); - var policyFactory = NSubstitute.Substitute.For>(); - - var sut = new SagaRunner(sagaFactory, sagaStateService, transactionManager, policyFactory, logger); - - await sut.RunAsync(messageContext, CancellationToken.None); - - await sagaStateService.Received(2) - .GetAsync(messageContext, Arg.Any()); - } - [Fact] public async Task RunAsync_should_not_execute_handler_if_message_already_processed() { @@ -193,8 +147,7 @@ public async Task RunAsync_should_throw_if_saga_cannot_handle_message() .Returns((state, Guid.NewGuid())); var saga = NSubstitute.Substitute.ForPartsOf(); - saga.SetBus(NSubstitute.Substitute.For()); - + var sagaFactory = NSubstitute.Substitute.For>(); sagaFactory.Create(state) .Returns(saga); @@ -224,7 +177,6 @@ public async Task RunAsync_should_execute_saga_handler_without_policy_when_not_a .Returns((state, Guid.NewGuid())); var saga = NSubstitute.Substitute.ForPartsOf(); - saga.SetBus(NSubstitute.Substitute.For()); saga.When(s => s.HandleAsync(Arg.Any>(), Arg.Any())) .DoNotCallBase(); @@ -261,7 +213,6 @@ public async Task RunAsync_should_execute_saga_handler_with_policy_when_availabl .Returns((state, Guid.NewGuid())); var saga = NSubstitute.Substitute.ForPartsOf(); - saga.SetBus(NSubstitute.Substitute.For()); saga.When(s => s.HandleAsync(Arg.Any>(), Arg.Any())) .DoNotCallBase(); @@ -300,7 +251,6 @@ public async Task RunAsync_should_use_transaction() .Returns((state, Guid.NewGuid())); var saga = NSubstitute.Substitute.ForPartsOf(); - saga.SetBus(NSubstitute.Substitute.For()); saga.When(s => s.HandleAsync(Arg.Any>(), Arg.Any())) .DoNotCallBase(); @@ -341,7 +291,6 @@ public async Task RunAsync_should_rollback_transaction_if_exception_occurs_and_r .Returns((state, Guid.NewGuid())); var saga = NSubstitute.Substitute.ForPartsOf(); - saga.SetBus(NSubstitute.Substitute.For()); var expectedException = new ApplicationException("whoops"); saga.When(s => s.HandleAsync(Arg.Any>(), Arg.Any())) @@ -396,7 +345,6 @@ public async Task RunAsync_should_rollback_transaction_when_exception_occurs_and }; var saga = new CompensatingSaga(onStart, onCompensate); - saga.SetBus(NSubstitute.Substitute.For()); var sagaFactory = NSubstitute.Substitute.For>(); sagaFactory.Create(state) diff --git a/tests/OpenSleigh.Core.Tests/Unit/SagaStateServiceTests.cs b/tests/OpenSleigh.Core.Tests/Unit/SagaStateServiceTests.cs index 44eca616..01c0144b 100644 --- a/tests/OpenSleigh.Core.Tests/Unit/SagaStateServiceTests.cs +++ b/tests/OpenSleigh.Core.Tests/Unit/SagaStateServiceTests.cs @@ -18,12 +18,16 @@ public void ctor_should_throw_if_arguments_null() { var sagaStateFactory = NSubstitute.Substitute.For>(); var sagaStateRepo = NSubstitute.Substitute.For(); + var outboxRepository = NSubstitute.Substitute.For(); + + Assert.Throws(() => + new SagaStateService(null, sagaStateRepo, outboxRepository)); Assert.Throws(() => - new SagaStateService(null, sagaStateRepo)); + new SagaStateService(sagaStateFactory, null, outboxRepository)); Assert.Throws(() => - new SagaStateService(sagaStateFactory, null)); + new SagaStateService(sagaStateFactory, sagaStateRepo, null)); } [Fact] @@ -31,8 +35,9 @@ public async Task GetAsync_should_throw_StateCreationException_if_saga_state_can { var sagaStateFactory = NSubstitute.Substitute.For>(); var sagaStateRepo = NSubstitute.Substitute.For(); - - var sut = new SagaStateService(sagaStateFactory, sagaStateRepo); + var outboxRepository = NSubstitute.Substitute.For(); + + var sut = new SagaStateService(sagaStateFactory, sagaStateRepo, outboxRepository); var message = StartDummySaga.New(); var messageContext = NSubstitute.Substitute.For>(); @@ -48,8 +53,9 @@ public async Task GetAsync_should_throw_MessageException_if_message_cannot_start { var sagaStateFactory = NSubstitute.Substitute.For>(); var sagaStateRepo = NSubstitute.Substitute.For(); - - var sut = new SagaStateService(sagaStateFactory, sagaStateRepo); + var outboxRepository = NSubstitute.Substitute.For(); + + var sut = new SagaStateService(sagaStateFactory, sagaStateRepo, outboxRepository); var message = DummySagaStarted.New(); var messageContext = NSubstitute.Substitute.For>(); @@ -80,7 +86,9 @@ public async Task GetAsync_should_return_state_from_factory_if_message_can_start sagaStateRepo.LockAsync(message.CorrelationId, expectedState, Arg.Any()) .Returns(Task.FromResult((expectedState, Guid.NewGuid()))); - var sut = new SagaStateService(sagaStateFactory, sagaStateRepo); + var outboxRepository = NSubstitute.Substitute.For(); + + var sut = new SagaStateService(sagaStateFactory, sagaStateRepo, outboxRepository); var result = await sut.GetAsync(messageContext, CancellationToken.None); result.state.Should().Be(expectedState); @@ -93,7 +101,9 @@ public async Task SaveAsync_should_unlock_state() var sagaStateRepo = NSubstitute.Substitute.For(); - var sut = new SagaStateService(sagaStateFactory, sagaStateRepo); + var outboxRepository = NSubstitute.Substitute.For(); + + var sut = new SagaStateService(sagaStateFactory, sagaStateRepo, outboxRepository); var state = new DummySagaState(Guid.NewGuid()); var lockId = Guid.NewGuid(); @@ -103,5 +113,28 @@ public async Task SaveAsync_should_unlock_state() await sagaStateRepo.Received(1) .ReleaseLockAsync(state, lockId, CancellationToken.None); } + + [Fact] + public async Task SaveAsync_should_persist_outbox() + { + var sagaStateFactory = NSubstitute.Substitute.For>(); + + var sagaStateRepo = NSubstitute.Substitute.For(); + + var outboxRepository = NSubstitute.Substitute.For(); + + var sut = new SagaStateService(sagaStateFactory, sagaStateRepo, outboxRepository); + + var state = new DummySagaState(Guid.NewGuid()); + + var message = StartDummySaga.New(); + state.AddToOutbox(message); + var lockId = Guid.NewGuid(); + + await sut.SaveAsync(state, lockId, CancellationToken.None); + + await outboxRepository.Received(1) + .AppendAsync(message, Arg.Any()); + } } } \ No newline at end of file diff --git a/tests/OpenSleigh.Core.Tests/Unit/SagaTests.cs b/tests/OpenSleigh.Core.Tests/Unit/SagaTests.cs index df84e68b..9b9fd367 100644 --- a/tests/OpenSleigh.Core.Tests/Unit/SagaTests.cs +++ b/tests/OpenSleigh.Core.Tests/Unit/SagaTests.cs @@ -1,6 +1,5 @@ using System; using FluentAssertions; -using OpenSleigh.Core.Messaging; using OpenSleigh.Core.Tests.Sagas; using Xunit; @@ -25,21 +24,6 @@ public void SetState_should_set_state() sut.State.Should().Be(state); } - [Fact] - public void SetBus_should_throw_when_input_null() - { - var sut = new DummySaga(); - Assert.Throws(() => sut.SetBus(null)); - } - - [Fact] - public void SetBus_should_set_state() - { - var sut = new DummySaga(); - - var bus = NSubstitute.Substitute.For(); - sut.SetBus(bus); - sut.Bus.Should().Be(bus); - } + //TODO: add tests for Publish } } diff --git a/tests/OpenSleigh.E2ETests/CosmosMongoServiceBus/CosmosMongoEventBroadcastingScenario.cs b/tests/OpenSleigh.E2ETests/CosmosMongoServiceBus/CosmosMongoEventBroadcastingScenario.cs new file mode 100644 index 00000000..e70c2f26 --- /dev/null +++ b/tests/OpenSleigh.E2ETests/CosmosMongoServiceBus/CosmosMongoEventBroadcastingScenario.cs @@ -0,0 +1,68 @@ +using Azure.Messaging.ServiceBus.Administration; +using MongoDB.Driver; +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Core.Tests.Sagas; +using OpenSleigh.Persistence.Cosmos.Mongo; +using OpenSleigh.Persistence.Cosmos.Mongo.Tests.Fixtures; +using OpenSleigh.Transport.AzureServiceBus; +using OpenSleigh.Transport.AzureServiceBus.Tests.Fixtures; +using System; +using System.Security.Authentication; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.CosmosMongoServiceBus +{ + public class CosmosMongoEventBroadcastingScenario : EventBroadcastingScenario, + IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly DbFixture _cosmosFixture; + private readonly ServiceBusFixture _sbFixture; + private readonly string _topicName; + private readonly string _subscriptionName; + + public CosmosMongoEventBroadcastingScenario(DbFixture cosmosFixture, ServiceBusFixture sbFixture) + { + _cosmosFixture = cosmosFixture; + _topicName = $"ServiceBusEventBroadcastingScenario.tests.{Guid.NewGuid()}"; + _subscriptionName = Guid.NewGuid().ToString(); + _sbFixture = sbFixture; + } + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var cosmosCfg = new CosmosConfiguration(_cosmosFixture.ConnectionString, + _cosmosFixture.DbName, + CosmosSagaStateRepositoryOptions.Default, + CosmosOutboxRepositoryOptions.Default); + + cfg.UseAzureServiceBusTransport(_sbFixture.Configuration, builder => + { + QueueReferencesPolicy policy = () => new QueueReferences(_topicName, _subscriptionName); + builder.UseMessageNamingPolicy(policy); + }) + .UseCosmosPersistence(cosmosCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseAzureServiceBusTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + var adminClient = new ServiceBusAdministrationClient(_sbFixture.Configuration.ConnectionString); + + await adminClient.DeleteSubscriptionAsync(_topicName, _subscriptionName); + await adminClient.DeleteTopicAsync(_topicName); + + var settings = MongoClientSettings.FromUrl(new MongoUrl(_cosmosFixture.ConnectionString)); + settings.SslSettings = new SslSettings() { EnabledSslProtocols = SslProtocols.Tls12 }; + var mongoClient = new MongoClient(settings); + await mongoClient.DropDatabaseAsync(_cosmosFixture.DbName); + } + } +} \ No newline at end of file diff --git a/tests/OpenSleigh.E2ETests/CosmosMongoServiceBus/CosmosMongoParentChildScenario.cs b/tests/OpenSleigh.E2ETests/CosmosMongoServiceBus/CosmosMongoParentChildScenario.cs new file mode 100644 index 00000000..da0b0bbe --- /dev/null +++ b/tests/OpenSleigh.E2ETests/CosmosMongoServiceBus/CosmosMongoParentChildScenario.cs @@ -0,0 +1,85 @@ +using Azure.Messaging.ServiceBus.Administration; +using MongoDB.Driver; +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Core.Tests.Sagas; +using OpenSleigh.Persistence.Cosmos.Mongo; +using OpenSleigh.Persistence.Cosmos.Mongo.Tests.Fixtures; +using OpenSleigh.Transport.AzureServiceBus; +using OpenSleigh.Transport.AzureServiceBus.Tests.Fixtures; +using System; +using System.Collections.Generic; +using System.Security.Authentication; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.CosmosMongoServiceBus +{ + public class CosmosMongoParentChildScenario : ParentChildScenario, IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly DbFixture _cosmosFixture; + private readonly ServiceBusFixture _sbFixture; + private readonly Dictionary _topics = new(); + + public CosmosMongoParentChildScenario(DbFixture fixture, ServiceBusFixture sbFixture) + { + _cosmosFixture = fixture; + + AddTopicName(); + AddTopicName(); + AddTopicName(); + AddTopicName(); + AddTopicName(); + AddTopicName(); + _sbFixture = sbFixture; + } + + private void AddTopicName() => + _topics[typeof(T)] = Guid.NewGuid().ToString(); + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var cosmosCfg = new CosmosConfiguration(_cosmosFixture.ConnectionString, + _cosmosFixture.DbName, + CosmosSagaStateRepositoryOptions.Default, + CosmosOutboxRepositoryOptions.Default); + + cfg.UseAzureServiceBusTransport(_sbFixture.Configuration, builder => + { + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(StartParentSaga)], _topics[typeof(StartParentSaga)])); + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ProcessParentSaga)], _topics[typeof(ProcessParentSaga)])); + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ParentSagaCompleted)], _topics[typeof(ParentSagaCompleted)])); + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(StartChildSaga)], _topics[typeof(StartChildSaga)])); + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ProcessChildSaga)], _topics[typeof(ProcessChildSaga)])); + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ChildSagaCompleted)], _topics[typeof(ChildSagaCompleted)])); + }) + .UseCosmosPersistence(cosmosCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseAzureServiceBusTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + var adminClient = new ServiceBusAdministrationClient(_sbFixture.Configuration.ConnectionString); + foreach (var topicName in _topics.Values) + await adminClient.DeleteTopicAsync(topicName); + + var settings = MongoClientSettings.FromUrl(new MongoUrl(_cosmosFixture.ConnectionString)); + settings.SslSettings = new SslSettings() { EnabledSslProtocols = SslProtocols.Tls12 }; + var mongoClient = new MongoClient(settings); + await mongoClient.DropDatabaseAsync(_cosmosFixture.DbName); + } + } + +} diff --git a/tests/OpenSleigh.E2ETests/CosmosMongoServiceBus/CosmosMongoSimpleSagaScenario.cs b/tests/OpenSleigh.E2ETests/CosmosMongoServiceBus/CosmosMongoSimpleSagaScenario.cs new file mode 100644 index 00000000..30baf028 --- /dev/null +++ b/tests/OpenSleigh.E2ETests/CosmosMongoServiceBus/CosmosMongoSimpleSagaScenario.cs @@ -0,0 +1,67 @@ +using Azure.Messaging.ServiceBus.Administration; +using MongoDB.Driver; +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Core.Tests.Sagas; +using OpenSleigh.Persistence.Cosmos.Mongo; +using OpenSleigh.Persistence.Cosmos.Mongo.Tests.Fixtures; +using OpenSleigh.Transport.AzureServiceBus; +using OpenSleigh.Transport.AzureServiceBus.Tests.Fixtures; +using System; +using System.Security.Authentication; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.CosmosMongoServiceBus +{ + public class CosmosMongoSimpleSagaScenario : SimpleSagaScenario, IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly DbFixture _cosmosFixture; + private readonly ServiceBusFixture _sbFixture; + private readonly string _topicName; + private readonly string _subscriptionName; + + public CosmosMongoSimpleSagaScenario(DbFixture fixture, ServiceBusFixture sbFixture) + { + _cosmosFixture = fixture; + + var messageName = nameof(StartSimpleSaga).ToLower(); + _topicName = $"{messageName}.tests.{Guid.NewGuid()}"; + _subscriptionName = $"{messageName}.workers"; + _sbFixture = sbFixture; + } + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var mongoCfg = new CosmosConfiguration(_cosmosFixture.ConnectionString, + _cosmosFixture.DbName, + CosmosSagaStateRepositoryOptions.Default, + CosmosOutboxRepositoryOptions.Default); + + cfg.UseAzureServiceBusTransport(_sbFixture.Configuration, builder => + { + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topicName, _subscriptionName)); + }) + .UseCosmosPersistence(mongoCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseAzureServiceBusTransport(); + + public async Task DisposeAsync() + { + var adminClient = new ServiceBusAdministrationClient(_sbFixture.Configuration.ConnectionString); + await adminClient.DeleteTopicAsync(_topicName); + + var settings = MongoClientSettings.FromUrl(new MongoUrl(_cosmosFixture.ConnectionString)); + settings.SslSettings = new SslSettings() { EnabledSslProtocols = SslProtocols.Tls12 }; + var mongoClient = new MongoClient(settings); + await mongoClient.DropDatabaseAsync(_cosmosFixture.DbName); + } + + public Task InitializeAsync() => Task.CompletedTask; + } +} diff --git a/tests/OpenSleigh.E2ETests/CosmosSQLServiceBus/CosmosSqlEventBroadcastingScenario.cs b/tests/OpenSleigh.E2ETests/CosmosSQLServiceBus/CosmosSqlEventBroadcastingScenario.cs new file mode 100644 index 00000000..a5e882ef --- /dev/null +++ b/tests/OpenSleigh.E2ETests/CosmosSQLServiceBus/CosmosSqlEventBroadcastingScenario.cs @@ -0,0 +1,59 @@ +using Azure.Messaging.ServiceBus.Administration; +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Core.Tests.Sagas; +using OpenSleigh.Persistence.Cosmos.SQL; +using OpenSleigh.Persistence.Cosmos.SQL.Tests.Fixtures; +using OpenSleigh.Transport.AzureServiceBus; +using OpenSleigh.Transport.AzureServiceBus.Tests.Fixtures; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.CosmosSQLServiceBus +{ + public class CosmosSqlEventBroadcastingScenario : EventBroadcastingScenario, + IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly DbFixture _fixture; + private readonly ServiceBusFixture _sbFixture; + private readonly string _topicName; + private readonly string _subscriptionName; + + public CosmosSqlEventBroadcastingScenario(DbFixture fixture, ServiceBusFixture sbFixture) + { + _fixture = fixture; + _sbFixture = sbFixture; + + _topicName = $"ServiceBusEventBroadcastingScenario.tests.{Guid.NewGuid()}"; + _subscriptionName = Guid.NewGuid().ToString(); + } + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var sqlCfg = new CosmosSqlConfiguration(_fixture.ConnectionString, _fixture.DbName); + + cfg.UseAzureServiceBusTransport(_sbFixture.Configuration, builder => + { + QueueReferencesPolicy policy = () => new QueueReferences(_topicName, _subscriptionName); + builder.UseMessageNamingPolicy(policy); + }) + .UseCosmosSqlPersistence(sqlCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseAzureServiceBusTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + var adminClient = new ServiceBusAdministrationClient(_sbFixture.Configuration.ConnectionString); + + await adminClient.DeleteSubscriptionAsync(_topicName, _subscriptionName); + await adminClient.DeleteTopicAsync(_topicName); + } + } +} \ No newline at end of file diff --git a/tests/OpenSleigh.E2ETests/CosmosSQLServiceBus/CosmosSqlParentChildScenario.cs b/tests/OpenSleigh.E2ETests/CosmosSQLServiceBus/CosmosSqlParentChildScenario.cs new file mode 100644 index 00000000..57c57266 --- /dev/null +++ b/tests/OpenSleigh.E2ETests/CosmosSQLServiceBus/CosmosSqlParentChildScenario.cs @@ -0,0 +1,76 @@ +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Persistence.Cosmos.SQL.Tests.Fixtures; +using Xunit; +using OpenSleigh.Transport.AzureServiceBus; +using System.Collections.Generic; +using System; +using OpenSleigh.Core.Tests.Sagas; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus.Administration; +using OpenSleigh.Transport.AzureServiceBus.Tests.Fixtures; +using OpenSleigh.Persistence.Cosmos.SQL; + +namespace OpenSleigh.E2ETests.CosmosSQLServiceBus +{ + public class CosmosSqlParentChildScenario : ParentChildScenario, + IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly DbFixture _cosmosFixture; + private readonly ServiceBusFixture _sbFixture; + private readonly Dictionary _topics = new(); + + public CosmosSqlParentChildScenario(DbFixture cosmosFixture, ServiceBusFixture sbFixture) + { + _cosmosFixture = cosmosFixture; + _sbFixture = sbFixture; + + AddTopicName(); + AddTopicName(); + AddTopicName(); + AddTopicName(); + AddTopicName(); + AddTopicName(); + } + + private void AddTopicName() => + _topics[typeof(T)] = Guid.NewGuid().ToString(); + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var sqlCfg = new CosmosSqlConfiguration(_cosmosFixture.ConnectionString, _cosmosFixture.DbName); + + cfg.UseAzureServiceBusTransport(_sbFixture.Configuration, builder => + { + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(StartParentSaga)], _topics[typeof(StartParentSaga)])); + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ProcessParentSaga)], _topics[typeof(ProcessParentSaga)])); + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ParentSagaCompleted)], _topics[typeof(ParentSagaCompleted)])); + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(StartChildSaga)], _topics[typeof(StartChildSaga)])); + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ProcessChildSaga)], _topics[typeof(ProcessChildSaga)])); + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ChildSagaCompleted)], _topics[typeof(ChildSagaCompleted)])); + }) + .UseCosmosSqlPersistence(sqlCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseAzureServiceBusTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + var adminClient = new ServiceBusAdministrationClient(_sbFixture.Configuration.ConnectionString); + foreach (var topicName in _topics.Values) + await adminClient.DeleteTopicAsync(topicName); + } + } + +} diff --git a/tests/OpenSleigh.E2ETests/CosmosSQLServiceBus/CosmosSqlSimpleSagaScenario.cs b/tests/OpenSleigh.E2ETests/CosmosSQLServiceBus/CosmosSqlSimpleSagaScenario.cs new file mode 100644 index 00000000..3c322e77 --- /dev/null +++ b/tests/OpenSleigh.E2ETests/CosmosSQLServiceBus/CosmosSqlSimpleSagaScenario.cs @@ -0,0 +1,59 @@ +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Persistence.Cosmos.SQL.Tests.Fixtures; +using Xunit; +using OpenSleigh.Transport.AzureServiceBus; +using OpenSleigh.Core.Tests.Sagas; +using System; +using System.Threading.Tasks; +using Azure.Messaging.ServiceBus.Administration; +using OpenSleigh.Transport.AzureServiceBus.Tests.Fixtures; +using OpenSleigh.Persistence.Cosmos.SQL; + +namespace OpenSleigh.E2ETests.CosmosSQLServiceBus +{ + public class CosmosSqlSimpleSagaScenario : SimpleSagaScenario, + IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly DbFixture _cosmosFixture; + private readonly ServiceBusFixture _sbFixture; + + private readonly string _topicName; + private readonly string _subscriptionName; + + public CosmosSqlSimpleSagaScenario(DbFixture cosmosFixture, ServiceBusFixture sbFixture) + { + _cosmosFixture = cosmosFixture; + _sbFixture = sbFixture; + + var messageName = nameof(StartSimpleSaga).ToLower(); + _topicName = $"{messageName}.tests.{Guid.NewGuid()}"; + _subscriptionName = $"{messageName}.workers"; + } + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var sqlCfg = new CosmosSqlConfiguration(_cosmosFixture.ConnectionString, _cosmosFixture.DbName); + + cfg.UseAzureServiceBusTransport(_sbFixture.Configuration, builder => + { + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topicName, _subscriptionName)); + }) + .UseCosmosSqlPersistence(sqlCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseAzureServiceBusTransport(); + + public async Task DisposeAsync() + { + var adminClient = new ServiceBusAdministrationClient(_sbFixture.Configuration.ConnectionString); + await adminClient.DeleteTopicAsync(_topicName); + } + + public Task InitializeAsync() => Task.CompletedTask; + } +} diff --git a/tests/OpenSleigh.E2ETests/MongoKafka/KafkaEventBroadcastingScenario.cs b/tests/OpenSleigh.E2ETests/MongoKafka/KafkaEventBroadcastingScenario.cs new file mode 100644 index 00000000..58181a46 --- /dev/null +++ b/tests/OpenSleigh.E2ETests/MongoKafka/KafkaEventBroadcastingScenario.cs @@ -0,0 +1,43 @@ +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Persistence.Mongo; +using OpenSleigh.Transport.Kafka; +using OpenSleigh.Transport.Kafka.Tests.Fixtures; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.MongoKafka +{ + public class KafkaEventBroadcastingScenario : EventBroadcastingScenario, + IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly KafkaFixture _fixture; + private readonly Persistence.Mongo.Tests.Fixtures.DbFixture _mongoFixture; + + public KafkaEventBroadcastingScenario(KafkaFixture fixture, Persistence.Mongo.Tests.Fixtures.DbFixture mongoFixture) + { + _fixture = fixture; + _mongoFixture = mongoFixture; + } + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var mongoCfg = new MongoConfiguration(_mongoFixture.ConnectionString, + _mongoFixture.DbName, + MongoSagaStateRepositoryOptions.Default, + MongoOutboxRepositoryOptions.Default); + + cfg.UseKafkaTransport(_fixture.KafkaConfiguration) + .UseMongoPersistence(mongoCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseKafkaTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public Task DisposeAsync() => Task.CompletedTask; + } +} \ No newline at end of file diff --git a/tests/OpenSleigh.E2ETests/MongoKafka/KafkaParentChildScenario.cs b/tests/OpenSleigh.E2ETests/MongoKafka/KafkaParentChildScenario.cs new file mode 100644 index 00000000..bec864bf --- /dev/null +++ b/tests/OpenSleigh.E2ETests/MongoKafka/KafkaParentChildScenario.cs @@ -0,0 +1,42 @@ +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Persistence.Mongo; +using OpenSleigh.Transport.Kafka; +using OpenSleigh.Transport.Kafka.Tests.Fixtures; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.MongoKafka +{ + public class KafkaParentChildScenario : ParentChildScenario, IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly KafkaFixture _fixture; + private readonly Persistence.Mongo.Tests.Fixtures.DbFixture _mongoFixture; + + public KafkaParentChildScenario(KafkaFixture fixture, Persistence.Mongo.Tests.Fixtures.DbFixture mongoFixture) + { + _fixture = fixture; + _mongoFixture = mongoFixture; + } + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var mongoCfg = new MongoConfiguration(_mongoFixture.ConnectionString, + _mongoFixture.DbName, + MongoSagaStateRepositoryOptions.Default, + MongoOutboxRepositoryOptions.Default); + + cfg.UseKafkaTransport(_fixture.KafkaConfiguration) + .UseMongoPersistence(mongoCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseKafkaTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public Task DisposeAsync() => Task.CompletedTask; + } +} diff --git a/tests/OpenSleigh.E2ETests/MongoKafka/KafkaSimpleSagaScenario.cs b/tests/OpenSleigh.E2ETests/MongoKafka/KafkaSimpleSagaScenario.cs new file mode 100644 index 00000000..53cf93cc --- /dev/null +++ b/tests/OpenSleigh.E2ETests/MongoKafka/KafkaSimpleSagaScenario.cs @@ -0,0 +1,43 @@ +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Persistence.Mongo; +using OpenSleigh.Transport.Kafka; +using OpenSleigh.Transport.Kafka.Tests.Fixtures; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.MongoKafka +{ + public class KafkaSimpleSagaScenario : SimpleSagaScenario, + IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly KafkaFixture _fixture; + private readonly Persistence.Mongo.Tests.Fixtures.DbFixture _mongoFixture; + + public KafkaSimpleSagaScenario(KafkaFixture fixture, Persistence.Mongo.Tests.Fixtures.DbFixture mongoFixture) + { + _fixture = fixture; + _mongoFixture = mongoFixture; + } + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var mongoCfg = new MongoConfiguration(_mongoFixture.ConnectionString, + _mongoFixture.DbName, + MongoSagaStateRepositoryOptions.Default, + MongoOutboxRepositoryOptions.Default); + + cfg.UseKafkaTransport(_fixture.KafkaConfiguration) + .UseMongoPersistence(mongoCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseKafkaTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public Task DisposeAsync() => Task.CompletedTask; + } +} diff --git a/tests/OpenSleigh.E2ETests/MongoRabbit/MongoRabbitEventBroadcastingScenario.cs b/tests/OpenSleigh.E2ETests/MongoRabbit/MongoRabbitEventBroadcastingScenario.cs new file mode 100644 index 00000000..945ee8cb --- /dev/null +++ b/tests/OpenSleigh.E2ETests/MongoRabbit/MongoRabbitEventBroadcastingScenario.cs @@ -0,0 +1,71 @@ +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Core.Tests.Sagas; +using OpenSleigh.Persistence.Mongo; +using OpenSleigh.Transport.RabbitMQ; +using OpenSleigh.Transport.RabbitMQ.Tests.Fixtures; +using RabbitMQ.Client; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.MongoRabbit +{ + public class MongoRabbitEventBroadcastingScenario : EventBroadcastingScenario, + IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly RabbitFixture _rabbitFixture; + private readonly Persistence.Mongo.Tests.Fixtures.DbFixture _mongoFixture; + private readonly string _exchangeName; + + public MongoRabbitEventBroadcastingScenario(RabbitFixture rabbitFixture, Persistence.Mongo.Tests.Fixtures.DbFixture mongoFixture) + { + _rabbitFixture = rabbitFixture; + _mongoFixture = mongoFixture; + _exchangeName = $"test.{nameof(DummyEvent)}.{Guid.NewGuid()}"; + } + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var mongoCfg = new MongoConfiguration(_mongoFixture.ConnectionString, + _mongoFixture.DbName, + MongoSagaStateRepositoryOptions.Default, + MongoOutboxRepositoryOptions.Default); + + cfg.UseRabbitMQTransport(_rabbitFixture.RabbitConfiguration, builder => { + builder.UseMessageNamingPolicy(() => + new QueueReferences(_exchangeName, + _exchangeName, + $"{_exchangeName}.dead", + $"{_exchangeName}.dead")); + }) + .UseMongoPersistence(mongoCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseRabbitMQTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + var connectionFactory = new ConnectionFactory() + { + HostName = _rabbitFixture.RabbitConfiguration.HostName, + UserName = _rabbitFixture.RabbitConfiguration.UserName, + Password = _rabbitFixture.RabbitConfiguration.Password, + Port = AmqpTcpEndpoint.UseDefaultPort, + DispatchConsumersAsync = true + }; + using var connection = connectionFactory.CreateConnection(); + using var channel = connection.CreateModel(); + channel.ExchangeDelete(_exchangeName); + channel.QueueDelete(_exchangeName); + channel.ExchangeDelete($"{_exchangeName}.dead"); + channel.QueueDelete($"{_exchangeName}.dead"); + + } + } +} \ No newline at end of file diff --git a/tests/OpenSleigh.E2ETests/MongoRabbit/MongoRabbitParentChildScenario.cs b/tests/OpenSleigh.E2ETests/MongoRabbit/MongoRabbitParentChildScenario.cs new file mode 100644 index 00000000..7ed5b76d --- /dev/null +++ b/tests/OpenSleigh.E2ETests/MongoRabbit/MongoRabbitParentChildScenario.cs @@ -0,0 +1,110 @@ +using MongoDB.Driver; +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Core.Tests.Sagas; +using OpenSleigh.Persistence.Mongo; +using OpenSleigh.Transport.RabbitMQ; +using OpenSleigh.Transport.RabbitMQ.Tests.Fixtures; +using RabbitMQ.Client; +using System; +using System.Collections.Generic; +using System.Security.Authentication; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.MongoRabbit +{ + public class MongoRabbitParentChildScenario : ParentChildScenario, + IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly RabbitFixture _rabbitFixture; + private readonly Persistence.Mongo.Tests.Fixtures.DbFixture _mongoFixture; + private readonly Dictionary _topics = new(); + + public MongoRabbitParentChildScenario(RabbitFixture fixture, Persistence.Mongo.Tests.Fixtures.DbFixture mongoFixture) + { + _rabbitFixture = fixture; + _mongoFixture = mongoFixture; + + AddTopicName(); + AddTopicName(); + AddTopicName(); + AddTopicName(); + AddTopicName(); + AddTopicName(); + } + + private void AddTopicName() => + _topics[typeof(T)] = Guid.NewGuid().ToString(); + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var mongoCfg = new MongoConfiguration(_mongoFixture.ConnectionString, + _mongoFixture.DbName, + MongoSagaStateRepositoryOptions.Default, + MongoOutboxRepositoryOptions.Default); + + cfg.UseRabbitMQTransport(_rabbitFixture.RabbitConfiguration, builder => + { + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(StartParentSaga)], _topics[typeof(StartParentSaga)], + _topics[typeof(StartParentSaga)] + ".dead", _topics[typeof(StartParentSaga)] + ".dead")); + + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ProcessParentSaga)], _topics[typeof(ProcessParentSaga)], + _topics[typeof(ProcessParentSaga)] + ".dead", _topics[typeof(ProcessParentSaga)] + ".dead")); + + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ParentSagaCompleted)], _topics[typeof(ParentSagaCompleted)], + _topics[typeof(ParentSagaCompleted)] + ".dead", _topics[typeof(ParentSagaCompleted)] + ".dead")); + + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(StartChildSaga)], _topics[typeof(StartChildSaga)], + _topics[typeof(StartChildSaga)] + ".dead", _topics[typeof(StartChildSaga)] + ".dead")); + + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ProcessChildSaga)], _topics[typeof(ProcessChildSaga)], + _topics[typeof(ProcessChildSaga)] + ".dead", _topics[typeof(ProcessChildSaga)] + ".dead")); + + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ChildSagaCompleted)], _topics[typeof(ChildSagaCompleted)], + _topics[typeof(ChildSagaCompleted)] + ".dead", _topics[typeof(ChildSagaCompleted)] + ".dead")); + }) + .UseMongoPersistence(mongoCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseRabbitMQTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + var settings = MongoClientSettings.FromUrl(new MongoUrl(_mongoFixture.ConnectionString)); + settings.SslSettings = new SslSettings() { EnabledSslProtocols = SslProtocols.Tls12 }; + var mongoClient = new MongoClient(settings); + await mongoClient.DropDatabaseAsync(_mongoFixture.DbName); + + var connectionFactory = new ConnectionFactory() + { + HostName = _rabbitFixture.RabbitConfiguration.HostName, + UserName = _rabbitFixture.RabbitConfiguration.UserName, + Password = _rabbitFixture.RabbitConfiguration.Password, + Port = AmqpTcpEndpoint.UseDefaultPort, + DispatchConsumersAsync = true + }; + using var connection = connectionFactory.CreateConnection(); + using var channel = connection.CreateModel(); + foreach(var kv in _topics) + { + channel.ExchangeDelete(kv.Value); + channel.QueueDelete(kv.Value); + + channel.ExchangeDelete(kv.Value + ".dead"); + channel.QueueDelete(kv.Value + ".dead"); + } + } + } +} diff --git a/tests/OpenSleigh.E2ETests/MongoRabbit/MongoRabbitSimpleSagaScenario.cs b/tests/OpenSleigh.E2ETests/MongoRabbit/MongoRabbitSimpleSagaScenario.cs new file mode 100644 index 00000000..f0cbd1de --- /dev/null +++ b/tests/OpenSleigh.E2ETests/MongoRabbit/MongoRabbitSimpleSagaScenario.cs @@ -0,0 +1,70 @@ +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Core.Tests.Sagas; +using OpenSleigh.Persistence.Mongo; +using OpenSleigh.Transport.RabbitMQ; +using OpenSleigh.Transport.RabbitMQ.Tests.Fixtures; +using RabbitMQ.Client; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.MongoRabbit +{ + public class MongoRabbitSimpleSagaScenario : SimpleSagaScenario, + IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly RabbitFixture _rabbitFixture; + private readonly Persistence.Mongo.Tests.Fixtures.DbFixture _mongoFixture; + private readonly string _exchangeName; + + public MongoRabbitSimpleSagaScenario(RabbitFixture rabbitFixture, Persistence.Mongo.Tests.Fixtures.DbFixture mongoFixture) + { + _rabbitFixture = rabbitFixture; + _mongoFixture = mongoFixture; + _exchangeName = $"test.{nameof(StartSimpleSaga)}.{Guid.NewGuid()}"; + } + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var mongoCfg = new MongoConfiguration(_mongoFixture.ConnectionString, + _mongoFixture.DbName, + MongoSagaStateRepositoryOptions.Default, + MongoOutboxRepositoryOptions.Default); + + cfg.UseRabbitMQTransport(_rabbitFixture.RabbitConfiguration, builder => { + builder.UseMessageNamingPolicy(() => + new QueueReferences(_exchangeName, + _exchangeName, + $"{_exchangeName}.dead", + $"{_exchangeName}.dead")); + }) + .UseMongoPersistence(mongoCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseRabbitMQTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + var connectionFactory = new ConnectionFactory() + { + HostName = _rabbitFixture.RabbitConfiguration.HostName, + UserName = _rabbitFixture.RabbitConfiguration.UserName, + Password = _rabbitFixture.RabbitConfiguration.Password, + Port = AmqpTcpEndpoint.UseDefaultPort, + DispatchConsumersAsync = true + }; + using var connection = connectionFactory.CreateConnection(); + using var channel = connection.CreateModel(); + channel.ExchangeDelete(_exchangeName); + channel.QueueDelete(_exchangeName); + channel.ExchangeDelete($"{_exchangeName}.dead"); + channel.QueueDelete($"{_exchangeName}.dead"); + } + } +} diff --git a/tests/OpenSleigh.E2ETests/OpenSleigh.E2ETests.csproj b/tests/OpenSleigh.E2ETests/OpenSleigh.E2ETests.csproj new file mode 100644 index 00000000..3be8b0b1 --- /dev/null +++ b/tests/OpenSleigh.E2ETests/OpenSleigh.E2ETests.csproj @@ -0,0 +1,42 @@ + + + + net5.0 + + false + + f3e44021-bff3-4b67-9b44-38638fa86bf7 + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + Always + + + + diff --git a/tests/OpenSleigh.E2ETests/SQLRabbit/SqlEventBroadcastingScenario.cs b/tests/OpenSleigh.E2ETests/SQLRabbit/SqlEventBroadcastingScenario.cs new file mode 100644 index 00000000..1a245ffc --- /dev/null +++ b/tests/OpenSleigh.E2ETests/SQLRabbit/SqlEventBroadcastingScenario.cs @@ -0,0 +1,68 @@ +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Core.Tests.Sagas; +using OpenSleigh.Persistence.SQL; +using OpenSleigh.Persistence.SQL.Tests.Fixtures; +using OpenSleigh.Transport.RabbitMQ; +using OpenSleigh.Transport.RabbitMQ.Tests.Fixtures; +using RabbitMQ.Client; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.SQLRabbit +{ + public class SqlEventBroadcastingScenario : EventBroadcastingScenario, + IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly DbFixture _fixture; + private readonly RabbitFixture _rabbitFixture; + private readonly string _exchangeName; + + public SqlEventBroadcastingScenario(DbFixture fixture, RabbitFixture rabbitFixture) + { + _fixture = fixture; + _exchangeName = $"test.{nameof(DummyEvent)}.{Guid.NewGuid()}"; + _rabbitFixture = rabbitFixture; + } + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var sqlCfg = new SqlConfiguration(_fixture.ConnectionString); + + cfg.UseRabbitMQTransport(_rabbitFixture.RabbitConfiguration, builder => { + builder.UseMessageNamingPolicy(() => + new QueueReferences(_exchangeName, + _exchangeName, + $"{_exchangeName}.dead", + $"{_exchangeName}.dead")); + }) + .UseSqlPersistence(sqlCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseRabbitMQTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + var connectionFactory = new ConnectionFactory() + { + HostName = _rabbitFixture.RabbitConfiguration.HostName, + UserName = _rabbitFixture.RabbitConfiguration.UserName, + Password = _rabbitFixture.RabbitConfiguration.Password, + Port = AmqpTcpEndpoint.UseDefaultPort, + DispatchConsumersAsync = true + }; + using var connection = connectionFactory.CreateConnection(); + using var channel = connection.CreateModel(); + channel.ExchangeDelete(_exchangeName); + channel.QueueDelete(_exchangeName); + channel.ExchangeDelete($"{_exchangeName}.dead"); + channel.QueueDelete($"{_exchangeName}.dead"); + } + } +} \ No newline at end of file diff --git a/tests/OpenSleigh.E2ETests/SQLRabbit/SqlParentChildScenario.cs b/tests/OpenSleigh.E2ETests/SQLRabbit/SqlParentChildScenario.cs new file mode 100644 index 00000000..b811c24a --- /dev/null +++ b/tests/OpenSleigh.E2ETests/SQLRabbit/SqlParentChildScenario.cs @@ -0,0 +1,103 @@ +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Core.Tests.Sagas; +using OpenSleigh.Persistence.SQL; +using OpenSleigh.Persistence.SQL.Tests.Fixtures; +using OpenSleigh.Transport.RabbitMQ; +using OpenSleigh.Transport.RabbitMQ.Tests.Fixtures; +using RabbitMQ.Client; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.SQLRabbit +{ + public class SqlParentChildScenario : ParentChildScenario, + IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly DbFixture _fixture; + private readonly RabbitFixture _rabbitFixture; + private readonly Dictionary _topics = new(); + + public SqlParentChildScenario(DbFixture fixture, RabbitFixture rabbitFixture) + { + _fixture = fixture; + _rabbitFixture = rabbitFixture; + AddTopicName(); + AddTopicName(); + AddTopicName(); + AddTopicName(); + AddTopicName(); + AddTopicName(); + } + + private void AddTopicName() => + _topics[typeof(T)] = Guid.NewGuid().ToString(); + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var sqlCfg = new SqlConfiguration(_fixture.ConnectionString); + + cfg.UseRabbitMQTransport(_rabbitFixture.RabbitConfiguration, builder => + { + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(StartParentSaga)], _topics[typeof(StartParentSaga)], + _topics[typeof(StartParentSaga)] + ".dead", _topics[typeof(StartParentSaga)] + ".dead")); + + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ProcessParentSaga)], _topics[typeof(ProcessParentSaga)], + _topics[typeof(ProcessParentSaga)] + ".dead", _topics[typeof(ProcessParentSaga)] + ".dead")); + + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ParentSagaCompleted)], _topics[typeof(ParentSagaCompleted)], + _topics[typeof(ParentSagaCompleted)] + ".dead", _topics[typeof(ParentSagaCompleted)] + ".dead")); + + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(StartChildSaga)], _topics[typeof(StartChildSaga)], + _topics[typeof(StartChildSaga)] + ".dead", _topics[typeof(StartChildSaga)] + ".dead")); + + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ProcessChildSaga)], _topics[typeof(ProcessChildSaga)], + _topics[typeof(ProcessChildSaga)] + ".dead", _topics[typeof(ProcessChildSaga)] + ".dead")); + + builder.UseMessageNamingPolicy(() => + new QueueReferences(_topics[typeof(ChildSagaCompleted)], _topics[typeof(ChildSagaCompleted)], + _topics[typeof(ChildSagaCompleted)] + ".dead", _topics[typeof(ChildSagaCompleted)] + ".dead")); + }) + .UseSqlPersistence(sqlCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseRabbitMQTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + //TODO: drop SQL db + + var connectionFactory = new ConnectionFactory() + { + HostName = _rabbitFixture.RabbitConfiguration.HostName, + UserName = _rabbitFixture.RabbitConfiguration.UserName, + Password = _rabbitFixture.RabbitConfiguration.Password, + Port = AmqpTcpEndpoint.UseDefaultPort, + DispatchConsumersAsync = true + }; + using var connection = connectionFactory.CreateConnection(); + using var channel = connection.CreateModel(); + foreach (var kv in _topics) + { + channel.ExchangeDelete(kv.Value); + channel.QueueDelete(kv.Value); + + channel.ExchangeDelete(kv.Value + ".dead"); + channel.QueueDelete(kv.Value + ".dead"); + } + } + } + +} diff --git a/tests/OpenSleigh.E2ETests/SQLRabbit/SqlSimpleSagaScenario.cs b/tests/OpenSleigh.E2ETests/SQLRabbit/SqlSimpleSagaScenario.cs new file mode 100644 index 00000000..6b57b6ea --- /dev/null +++ b/tests/OpenSleigh.E2ETests/SQLRabbit/SqlSimpleSagaScenario.cs @@ -0,0 +1,68 @@ +using OpenSleigh.Core.DependencyInjection; +using OpenSleigh.Core.Tests.E2E; +using OpenSleigh.Core.Tests.Sagas; +using OpenSleigh.Persistence.SQL; +using OpenSleigh.Persistence.SQL.Tests.Fixtures; +using OpenSleigh.Transport.RabbitMQ; +using OpenSleigh.Transport.RabbitMQ.Tests.Fixtures; +using RabbitMQ.Client; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace OpenSleigh.E2ETests.SQLRabbit +{ + public class SqlSimpleSagaScenario : SimpleSagaScenario, + IClassFixture, + IClassFixture, + IAsyncLifetime + { + private readonly DbFixture _fixture; + private readonly RabbitFixture _rabbitFixture; + private readonly string _exchangeName; + + public SqlSimpleSagaScenario(DbFixture fixture, RabbitFixture rabbitFixture) + { + _fixture = fixture; + _rabbitFixture = rabbitFixture; + _exchangeName = $"test.{nameof(StartSimpleSaga)}.{Guid.NewGuid()}"; + } + + protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) + { + var sqlCfg = new SqlConfiguration(_fixture.ConnectionString); + + cfg.UseRabbitMQTransport(_rabbitFixture.RabbitConfiguration, builder => { + builder.UseMessageNamingPolicy(() => + new QueueReferences(_exchangeName, + _exchangeName, + $"{_exchangeName}.dead", + $"{_exchangeName}.dead")); + }) + .UseSqlPersistence(sqlCfg); + } + + protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => + cfg.UseRabbitMQTransport(); + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + var connectionFactory = new ConnectionFactory() + { + HostName = _rabbitFixture.RabbitConfiguration.HostName, + UserName = _rabbitFixture.RabbitConfiguration.UserName, + Password = _rabbitFixture.RabbitConfiguration.Password, + Port = AmqpTcpEndpoint.UseDefaultPort, + DispatchConsumersAsync = true + }; + using var connection = connectionFactory.CreateConnection(); + using var channel = connection.CreateModel(); + channel.ExchangeDelete(_exchangeName); + channel.QueueDelete(_exchangeName); + channel.ExchangeDelete($"{_exchangeName}.dead"); + channel.QueueDelete($"{_exchangeName}.dead"); + } + } +} diff --git a/tests/OpenSleigh.E2ETests/appsettings.json b/tests/OpenSleigh.E2ETests/appsettings.json new file mode 100644 index 00000000..ac30e800 --- /dev/null +++ b/tests/OpenSleigh.E2ETests/appsettings.json @@ -0,0 +1,22 @@ +{ + "ConnectionStrings": { + "mongo": "mongodb://root:password@127.0.0.1:27017", + "sql": "Server=127.0.0.1;Database=OpenSleighTests_{0};User=sa;Password=Sup3r_p4ssword123;", + "kafka": "127.0.0.1:9092" + }, + "Kafka": { + "ConsumerGroup": "OpenSleigh.Kafka.Tests" + }, + "Rabbit": { + "Hostname": "127.0.0.1", + "UserName": "guest", + "Password": "guest" + }, + "Logging": { + "LogLevel": { + "Default": "Trace", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/E2E/CosmosMongoEventBroadcastingScenario.cs b/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/E2E/CosmosMongoEventBroadcastingScenario.cs deleted file mode 100644 index b0a8abd1..00000000 --- a/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/E2E/CosmosMongoEventBroadcastingScenario.cs +++ /dev/null @@ -1,32 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Persistence.Cosmos.Mongo.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Persistence.Cosmos.Mongo.Tests.E2E -{ - public class CosmosMongoEventBroadcastingScenario : EventBroadcastingScenario, IClassFixture - { - private readonly DbFixture _fixture; - - public CosmosMongoEventBroadcastingScenario(DbFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - var cosmosCfg = new CosmosConfiguration(_fixture.ConnectionString, - _fixture.DbName, - CosmosSagaStateRepositoryOptions.Default, - CosmosOutboxRepositoryOptions.Default); - - cfg.UseInMemoryTransport() - .UseCosmosPersistence(cosmosCfg); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseInMemoryTransport(); - } -} \ No newline at end of file diff --git a/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/E2E/CosmosMongoParentChildScenario.cs b/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/E2E/CosmosMongoParentChildScenario.cs deleted file mode 100644 index cf5bb39b..00000000 --- a/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/E2E/CosmosMongoParentChildScenario.cs +++ /dev/null @@ -1,33 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Persistence.Cosmos.Mongo.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Persistence.Cosmos.Mongo.Tests.E2E -{ - public class CosmosMongoParentChildScenario : ParentChildScenario, IClassFixture - { - private readonly DbFixture _fixture; - - public CosmosMongoParentChildScenario(DbFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - var cosmosCfg = new CosmosConfiguration(_fixture.ConnectionString, - _fixture.DbName, - CosmosSagaStateRepositoryOptions.Default, - CosmosOutboxRepositoryOptions.Default); - - cfg.UseInMemoryTransport() - .UseCosmosPersistence(cosmosCfg); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseInMemoryTransport(); - } - -} diff --git a/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/E2E/CosmosMongoSimpleSagaScenario.cs b/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/E2E/CosmosMongoSimpleSagaScenario.cs deleted file mode 100644 index 08c0eba0..00000000 --- a/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/E2E/CosmosMongoSimpleSagaScenario.cs +++ /dev/null @@ -1,32 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Persistence.Cosmos.Mongo.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Persistence.Cosmos.Mongo.Tests.E2E -{ - public class CosmosMongoSimpleSagaScenario : SimpleSagaScenario, IClassFixture - { - private readonly DbFixture _fixture; - - public CosmosMongoSimpleSagaScenario(DbFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - var mongoCfg = new CosmosConfiguration(_fixture.ConnectionString, - _fixture.DbName, - CosmosSagaStateRepositoryOptions.Default, - CosmosOutboxRepositoryOptions.Default); - - cfg.UseInMemoryTransport() - .UseCosmosPersistence(mongoCfg); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseInMemoryTransport(); - } -} diff --git a/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests.csproj b/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests.csproj index 34e2f08b..f9db21f7 100644 --- a/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests.csproj +++ b/tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests/OpenSleigh.Persistence.Cosmos.Mongo.Tests.csproj @@ -35,7 +35,6 @@ - diff --git a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/E2E/CosmosSqlEventBroadcastingScenario.cs b/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/E2E/CosmosSqlEventBroadcastingScenario.cs deleted file mode 100644 index f1051924..00000000 --- a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/E2E/CosmosSqlEventBroadcastingScenario.cs +++ /dev/null @@ -1,29 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Persistence.Cosmos.SQL.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Persistence.Cosmos.SQL.Tests.E2E -{ - public class CosmosSqlEventBroadcastingScenario : EventBroadcastingScenario, IClassFixture - { - private readonly DbFixture _fixture; - - public CosmosSqlEventBroadcastingScenario(DbFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - var sqlCfg = new CosmosSqlConfiguration(_fixture.ConnectionString, _fixture.DbName); - - cfg.UseInMemoryTransport() - .UseCosmosSqlPersistence(sqlCfg); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseInMemoryTransport(); - } -} \ No newline at end of file diff --git a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/E2E/CosmosSqlParentChildScenario.cs b/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/E2E/CosmosSqlParentChildScenario.cs deleted file mode 100644 index 2cacb0f7..00000000 --- a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/E2E/CosmosSqlParentChildScenario.cs +++ /dev/null @@ -1,30 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Persistence.Cosmos.SQL.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Persistence.Cosmos.SQL.Tests.E2E -{ - public class CosmosSqlParentChildScenario : ParentChildScenario, IClassFixture - { - private readonly DbFixture _fixture; - - public CosmosSqlParentChildScenario(DbFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - var sqlCfg = new CosmosSqlConfiguration(_fixture.ConnectionString, _fixture.DbName); - - cfg.UseInMemoryTransport() - .UseCosmosSqlPersistence(sqlCfg); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseInMemoryTransport(); - } - -} diff --git a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/E2E/CosmosSqlSimpleSagaScenario.cs b/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/E2E/CosmosSqlSimpleSagaScenario.cs deleted file mode 100644 index d7bdbe95..00000000 --- a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/E2E/CosmosSqlSimpleSagaScenario.cs +++ /dev/null @@ -1,29 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Persistence.Cosmos.SQL.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Persistence.Cosmos.SQL.Tests.E2E -{ - public class CosmosSqlSimpleSagaScenario : SimpleSagaScenario, IClassFixture - { - private readonly DbFixture _fixture; - - public CosmosSqlSimpleSagaScenario(DbFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - var sqlCfg = new CosmosSqlConfiguration(_fixture.ConnectionString, _fixture.DbName); - - cfg.UseInMemoryTransport() - .UseCosmosSqlPersistence(sqlCfg); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseInMemoryTransport(); - } -} diff --git a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/Integration/CosmosSqlOutboxRepositoryTests.cs b/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/Integration/CosmosSqlOutboxRepositoryTests.cs index 1d0b26cd..8c9853d8 100644 --- a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/Integration/CosmosSqlOutboxRepositoryTests.cs +++ b/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/Integration/CosmosSqlOutboxRepositoryTests.cs @@ -107,7 +107,7 @@ public async Task LockAsync_should_throw_if_message_already_processed() var lockId = await sut.LockAsync(message); await sut.ReleaseAsync(message, lockId); - await Assert.ThrowsAsync(async () => await sut.LockAsync(message)); + await Assert.ThrowsAsync(async () => await sut.LockAsync(message)); } [Fact] @@ -126,8 +126,8 @@ public async Task ReleaseAsync_should_throw_if_message_not_locked() await sut.AppendAsync(message); - var ex = await Assert.ThrowsAsync(async () => await sut.ReleaseAsync(message, Guid.NewGuid())); - ex.Message.Should().Contain($"message '{message.Id}' is not locked"); + var ex = await Assert.ThrowsAsync(async () => await sut.ReleaseAsync(message, Guid.NewGuid())); + ex.Message.Should().Contain($"message '{message.Id}' not found"); } [Fact] @@ -141,8 +141,8 @@ public async Task ReleaseAsync_should_throw_if_message_already_locked() var lockId = Guid.NewGuid(); - var ex = await Assert.ThrowsAsync(async () => await sut.ReleaseAsync(message, lockId)); - ex.Message.Should().Contain($"invalid lock id '{lockId}' on message '{message.Id}'"); + var ex = await Assert.ThrowsAsync(async () => await sut.ReleaseAsync(message, lockId)); + ex.Message.Should().Contain($"message '{message.Id}' not found"); } [Fact] diff --git a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/Integration/CosmosSqlSagaStateRepositoryTests.cs b/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/Integration/CosmosSqlSagaStateRepositoryTests.cs index 2a5e3ad1..1ec99fc0 100644 --- a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/Integration/CosmosSqlSagaStateRepositoryTests.cs +++ b/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/Integration/CosmosSqlSagaStateRepositoryTests.cs @@ -109,7 +109,7 @@ public async Task ReleaseLockAsync_should_throw_when_state_not_found() var state = new DummyState(correlationId, "lorem", 42); var ex = await Assert.ThrowsAsync(async () => await sut.ReleaseLockAsync(state, Guid.NewGuid())); - ex.Message.Should().Contain($"unable to find Saga State '{state.Id}'"); + ex.Message.Should().Contain($"unable to release Saga State '{state.Id}'"); } [Fact] diff --git a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/OpenSleigh.Persistence.Cosmos.SQL.Tests.csproj b/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/OpenSleigh.Persistence.Cosmos.SQL.Tests.csproj index e954ef9a..f246e1b2 100644 --- a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/OpenSleigh.Persistence.Cosmos.SQL.Tests.csproj +++ b/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/OpenSleigh.Persistence.Cosmos.SQL.Tests.csproj @@ -33,8 +33,7 @@ - - + diff --git a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/Unit/Entities/OutboxMessageTests.cs b/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/Unit/Entities/OutboxMessageTests.cs index 35afcf34..e5e9794b 100644 --- a/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/Unit/Entities/OutboxMessageTests.cs +++ b/tests/OpenSleigh.Persistence.Cosmos.SQL.Tests/Unit/Entities/OutboxMessageTests.cs @@ -1,15 +1,11 @@ using FluentAssertions; using OpenSleigh.Persistence.Cosmos.SQL.Entities; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace OpenSleigh.Persistence.Cosmos.SQL.Tests.Unit.Entities { - public class OutboxMessageTests + public class OutboxMessageTests { [Fact] public void New_should_create_valid_instance() diff --git a/tests/OpenSleigh.Persistence.InMemory.Tests/E2E/InMemoryEventBroadcastingScenario.cs b/tests/OpenSleigh.Persistence.InMemory.Tests/E2E/InMemoryEventBroadcastingScenario.cs deleted file mode 100644 index 92b0609d..00000000 --- a/tests/OpenSleigh.Persistence.InMemory.Tests/E2E/InMemoryEventBroadcastingScenario.cs +++ /dev/null @@ -1,14 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; - -namespace OpenSleigh.Persistence.InMemory.Tests.E2E -{ - public class InMemoryEventBroadcastingScenario : EventBroadcastingScenario - { - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) => - cfg.UseInMemoryTransport() - .UseInMemoryPersistence(); - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => cfg.UseInMemoryTransport(); - } -} \ No newline at end of file diff --git a/tests/OpenSleigh.Persistence.InMemory.Tests/E2E/InMemoryParentChildScenario.cs b/tests/OpenSleigh.Persistence.InMemory.Tests/E2E/InMemoryParentChildScenario.cs deleted file mode 100644 index 3e2c5f5b..00000000 --- a/tests/OpenSleigh.Persistence.InMemory.Tests/E2E/InMemoryParentChildScenario.cs +++ /dev/null @@ -1,15 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; - -namespace OpenSleigh.Persistence.InMemory.Tests.E2E -{ - public class InMemoryParentChildScenario : ParentChildScenario - { - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) => - cfg.UseInMemoryTransport() - .UseInMemoryPersistence(); - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => cfg.UseInMemoryTransport(); - } - -} diff --git a/tests/OpenSleigh.Persistence.InMemory.Tests/E2E/InMemorySimpleSagaScenario.cs b/tests/OpenSleigh.Persistence.InMemory.Tests/E2E/InMemorySimpleSagaScenario.cs deleted file mode 100644 index 4e67d4c2..00000000 --- a/tests/OpenSleigh.Persistence.InMemory.Tests/E2E/InMemorySimpleSagaScenario.cs +++ /dev/null @@ -1,14 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; - -namespace OpenSleigh.Persistence.InMemory.Tests.E2E -{ - public class InMemorySimpleSagaScenario : SimpleSagaScenario - { - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) => - cfg.UseInMemoryTransport() - .UseInMemoryPersistence(); - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => cfg.UseInMemoryTransport(); - } -} diff --git a/tests/OpenSleigh.Persistence.Mongo.Tests/E2E/MongoEventBroadcastingScenario.cs b/tests/OpenSleigh.Persistence.Mongo.Tests/E2E/MongoEventBroadcastingScenario.cs deleted file mode 100644 index 3000b73d..00000000 --- a/tests/OpenSleigh.Persistence.Mongo.Tests/E2E/MongoEventBroadcastingScenario.cs +++ /dev/null @@ -1,32 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Persistence.Mongo.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Persistence.Mongo.Tests.E2E -{ - public class MongoEventBroadcastingScenario : EventBroadcastingScenario, IClassFixture - { - private readonly DbFixture _fixture; - - public MongoEventBroadcastingScenario(DbFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - var mongoCfg = new MongoConfiguration(_fixture.ConnectionString, - _fixture.DbName, - MongoSagaStateRepositoryOptions.Default, - MongoOutboxRepositoryOptions.Default); - - cfg.UseInMemoryTransport() - .UseMongoPersistence(mongoCfg); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseInMemoryTransport(); - } -} \ No newline at end of file diff --git a/tests/OpenSleigh.Persistence.Mongo.Tests/E2E/MongoParentChildScenario.cs b/tests/OpenSleigh.Persistence.Mongo.Tests/E2E/MongoParentChildScenario.cs deleted file mode 100644 index d6bf5940..00000000 --- a/tests/OpenSleigh.Persistence.Mongo.Tests/E2E/MongoParentChildScenario.cs +++ /dev/null @@ -1,33 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Persistence.Mongo.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Persistence.Mongo.Tests.E2E -{ - public class MongoParentChildScenario : ParentChildScenario, IClassFixture - { - private readonly DbFixture _fixture; - - public MongoParentChildScenario(DbFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - var mongoCfg = new MongoConfiguration(_fixture.ConnectionString, - _fixture.DbName, - MongoSagaStateRepositoryOptions.Default, - MongoOutboxRepositoryOptions.Default); - - cfg.UseInMemoryTransport() - .UseMongoPersistence(mongoCfg); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseInMemoryTransport(); - } - -} diff --git a/tests/OpenSleigh.Persistence.Mongo.Tests/E2E/MongoSimpleSagaScenario.cs b/tests/OpenSleigh.Persistence.Mongo.Tests/E2E/MongoSimpleSagaScenario.cs deleted file mode 100644 index 652e95ac..00000000 --- a/tests/OpenSleigh.Persistence.Mongo.Tests/E2E/MongoSimpleSagaScenario.cs +++ /dev/null @@ -1,32 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Persistence.Mongo.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Persistence.Mongo.Tests.E2E -{ - public class MongoSimpleSagaScenario : SimpleSagaScenario, IClassFixture - { - private readonly DbFixture _fixture; - - public MongoSimpleSagaScenario(DbFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - var mongoCfg = new MongoConfiguration(_fixture.ConnectionString, - _fixture.DbName, - MongoSagaStateRepositoryOptions.Default, - MongoOutboxRepositoryOptions.Default); - - cfg.UseInMemoryTransport() - .UseMongoPersistence(mongoCfg); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseInMemoryTransport(); - } -} diff --git a/tests/OpenSleigh.Persistence.Mongo.Tests/Fixtures/DbFixture.cs b/tests/OpenSleigh.Persistence.Mongo.Tests/Fixtures/DbFixture.cs index f99f24f8..86ba203b 100644 --- a/tests/OpenSleigh.Persistence.Mongo.Tests/Fixtures/DbFixture.cs +++ b/tests/OpenSleigh.Persistence.Mongo.Tests/Fixtures/DbFixture.cs @@ -18,7 +18,7 @@ public DbFixture() .AddEnvironmentVariables() .Build(); - this.ConnectionString = configuration.GetConnectionString("mongo"); + this.ConnectionString = configuration.GetConnectionString("mongo"); if (string.IsNullOrWhiteSpace(this.ConnectionString)) throw new ArgumentException("invalid connection string"); @@ -29,7 +29,7 @@ public DbFixture() DbContext = new DbContext(_db); } - + public string ConnectionString { get; init; } public string DbName { get; init; } diff --git a/tests/OpenSleigh.Persistence.Mongo.Tests/OpenSleigh.Persistence.Mongo.Tests.csproj b/tests/OpenSleigh.Persistence.Mongo.Tests/OpenSleigh.Persistence.Mongo.Tests.csproj index e1b2fd67..3162b0bc 100644 --- a/tests/OpenSleigh.Persistence.Mongo.Tests/OpenSleigh.Persistence.Mongo.Tests.csproj +++ b/tests/OpenSleigh.Persistence.Mongo.Tests/OpenSleigh.Persistence.Mongo.Tests.csproj @@ -31,7 +31,6 @@ - diff --git a/tests/OpenSleigh.Persistence.SQL.Tests/E2E/SqlEventBroadcastingScenario.cs b/tests/OpenSleigh.Persistence.SQL.Tests/E2E/SqlEventBroadcastingScenario.cs deleted file mode 100644 index 46a45dd0..00000000 --- a/tests/OpenSleigh.Persistence.SQL.Tests/E2E/SqlEventBroadcastingScenario.cs +++ /dev/null @@ -1,29 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Persistence.SQL.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Persistence.SQL.Tests.E2E -{ - public class SqlEventBroadcastingScenario : EventBroadcastingScenario, IClassFixture - { - private readonly DbFixture _fixture; - - public SqlEventBroadcastingScenario(DbFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - var sqlCfg = new SqlConfiguration(_fixture.ConnectionString); - - cfg.UseInMemoryTransport() - .UseSqlPersistence(sqlCfg); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseInMemoryTransport(); - } -} \ No newline at end of file diff --git a/tests/OpenSleigh.Persistence.SQL.Tests/E2E/SqlParentChildScenario.cs b/tests/OpenSleigh.Persistence.SQL.Tests/E2E/SqlParentChildScenario.cs deleted file mode 100644 index 2ed92b9f..00000000 --- a/tests/OpenSleigh.Persistence.SQL.Tests/E2E/SqlParentChildScenario.cs +++ /dev/null @@ -1,30 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Persistence.SQL.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Persistence.SQL.Tests.E2E -{ - public class SqlParentChildScenario : ParentChildScenario, IClassFixture - { - private readonly DbFixture _fixture; - - public SqlParentChildScenario(DbFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - var sqlCfg = new SqlConfiguration(_fixture.ConnectionString); - - cfg.UseInMemoryTransport() - .UseSqlPersistence(sqlCfg); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseInMemoryTransport(); - } - -} diff --git a/tests/OpenSleigh.Persistence.SQL.Tests/E2E/SqlSimpleSagaScenario.cs b/tests/OpenSleigh.Persistence.SQL.Tests/E2E/SqlSimpleSagaScenario.cs deleted file mode 100644 index 09f488cf..00000000 --- a/tests/OpenSleigh.Persistence.SQL.Tests/E2E/SqlSimpleSagaScenario.cs +++ /dev/null @@ -1,29 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Persistence.SQL.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Persistence.SQL.Tests.E2E -{ - public class SqlSimpleSagaScenario : SimpleSagaScenario, IClassFixture - { - private readonly DbFixture _fixture; - - public SqlSimpleSagaScenario(DbFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - var sqlCfg = new SqlConfiguration(_fixture.ConnectionString); - - cfg.UseInMemoryTransport() - .UseSqlPersistence(sqlCfg); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseInMemoryTransport(); - } -} diff --git a/tests/OpenSleigh.Persistence.SQL.Tests/Integration/SqlOutboxRepositoryTests.cs b/tests/OpenSleigh.Persistence.SQL.Tests/Integration/SqlOutboxRepositoryTests.cs index 078c8a56..69b6f04b 100644 --- a/tests/OpenSleigh.Persistence.SQL.Tests/Integration/SqlOutboxRepositoryTests.cs +++ b/tests/OpenSleigh.Persistence.SQL.Tests/Integration/SqlOutboxRepositoryTests.cs @@ -103,7 +103,7 @@ public async Task LockAsync_should_throw_if_message_already_processed() var lockId = await sut.LockAsync(message); await sut.ReleaseAsync(message, lockId); - await Assert.ThrowsAsync(async () => await sut.LockAsync(message)); + await Assert.ThrowsAsync(async () => await sut.LockAsync(message)); } [Fact] @@ -122,8 +122,8 @@ public async Task ReleaseAsync_should_throw_if_message_not_locked() await sut.AppendAsync(message); - var ex = await Assert.ThrowsAsync(async () => await sut.ReleaseAsync(message, Guid.NewGuid())); - ex.Message.Should().Contain($"message '{message.Id}' is not locked"); + var ex = await Assert.ThrowsAsync(async () => await sut.ReleaseAsync(message, Guid.NewGuid())); + ex.Message.Should().Contain($"message '{message.Id}' not found"); } [Fact] @@ -137,8 +137,8 @@ public async Task ReleaseAsync_should_throw_if_message_already_locked() var lockId = Guid.NewGuid(); - var ex = await Assert.ThrowsAsync(async () => await sut.ReleaseAsync(message, lockId)); - ex.Message.Should().Contain($"invalid lock id '{lockId}' on message '{message.Id}'"); + var ex = await Assert.ThrowsAsync(async () => await sut.ReleaseAsync(message, lockId)); + ex.Message.Should().Contain($"message '{message.Id}' not found"); } [Fact] diff --git a/tests/OpenSleigh.Persistence.SQL.Tests/Integration/SqlSagaStateRepositoryTests.cs b/tests/OpenSleigh.Persistence.SQL.Tests/Integration/SqlSagaStateRepositoryTests.cs index 43ec5bb8..55788197 100644 --- a/tests/OpenSleigh.Persistence.SQL.Tests/Integration/SqlSagaStateRepositoryTests.cs +++ b/tests/OpenSleigh.Persistence.SQL.Tests/Integration/SqlSagaStateRepositoryTests.cs @@ -108,7 +108,7 @@ public async Task ReleaseLockAsync_should_throw_when_state_not_found() var state = new DummyState(correlationId, "lorem", 42); var ex = await Assert.ThrowsAsync(async () => await sut.ReleaseLockAsync(state, Guid.NewGuid())); - ex.Message.Should().Contain($"unable to find Saga State '{state.Id}'"); + ex.Message.Should().Contain($"unable to release Saga State"); } [Fact] diff --git a/tests/OpenSleigh.Persistence.SQL.Tests/OpenSleigh.Persistence.SQL.Tests.csproj b/tests/OpenSleigh.Persistence.SQL.Tests/OpenSleigh.Persistence.SQL.Tests.csproj index 3553b8a7..2ab4de07 100644 --- a/tests/OpenSleigh.Persistence.SQL.Tests/OpenSleigh.Persistence.SQL.Tests.csproj +++ b/tests/OpenSleigh.Persistence.SQL.Tests/OpenSleigh.Persistence.SQL.Tests.csproj @@ -30,8 +30,7 @@ - - + diff --git a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/E2E/ServiceBusEventBroadcastingScenario.cs b/tests/OpenSleigh.Transport.AzureServiceBus.Tests/E2E/ServiceBusEventBroadcastingScenario.cs deleted file mode 100644 index 341aa866..00000000 --- a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/E2E/ServiceBusEventBroadcastingScenario.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Azure.Messaging.ServiceBus.Administration; -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Core.Tests.Sagas; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Transport.AzureServiceBus.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Transport.AzureServiceBus.Tests.E2E -{ - public class ServiceBusEventBroadcastingScenario : EventBroadcastingScenario, IClassFixture, IAsyncLifetime - { - private readonly ServiceBusFixture _fixture; - private readonly string _topicName; - private readonly string _subscriptionName; - - public ServiceBusEventBroadcastingScenario(ServiceBusFixture fixture) - { - _fixture = fixture; - _topicName = $"ServiceBusEventBroadcastingScenario.tests.{Guid.NewGuid()}"; - _subscriptionName = Guid.NewGuid().ToString(); - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - cfg.UseAzureServiceBusTransport(_fixture.Configuration, builder => - { - QueueReferencesPolicy policy = () => new QueueReferences(_topicName, _subscriptionName); - builder.UseMessageNamingPolicy(policy); - }) - .UseInMemoryPersistence(); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseAzureServiceBusTransport(); - - public Task InitializeAsync() => Task.CompletedTask; - - public async Task DisposeAsync() - { - var adminClient = new ServiceBusAdministrationClient(_fixture.Configuration.ConnectionString); - - await adminClient.DeleteSubscriptionAsync(_topicName, _subscriptionName); - await adminClient.DeleteTopicAsync(_topicName); - } - } -} \ No newline at end of file diff --git a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/E2E/ServiceBusParentChildScenario.cs b/tests/OpenSleigh.Transport.AzureServiceBus.Tests/E2E/ServiceBusParentChildScenario.cs deleted file mode 100644 index 1ec129e8..00000000 --- a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/E2E/ServiceBusParentChildScenario.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Azure.Messaging.ServiceBus.Administration; -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Core.Tests.Sagas; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Transport.AzureServiceBus.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Transport.AzureServiceBus.Tests.E2E -{ - public class ServiceBusParentChildScenario : ParentChildScenario, IClassFixture, IAsyncLifetime - { - private readonly ServiceBusFixture _fixture; - - private readonly Dictionary _topics = new(); - - public ServiceBusParentChildScenario(ServiceBusFixture fixture) - { - _fixture = fixture; - - AddTopicName(); - AddTopicName(); - AddTopicName(); - AddTopicName(); - AddTopicName(); - AddTopicName(); - } - - private void AddTopicName() => - _topics[typeof(T)] = Guid.NewGuid().ToString(); - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - cfg.UseAzureServiceBusTransport(_fixture.Configuration, builder => - { - builder.UseMessageNamingPolicy(() => - new QueueReferences(_topics[typeof(StartParentSaga)], _topics[typeof(StartParentSaga)])); - builder.UseMessageNamingPolicy(() => - new QueueReferences(_topics[typeof(ProcessParentSaga)], _topics[typeof(ProcessParentSaga)])); - builder.UseMessageNamingPolicy(() => - new QueueReferences(_topics[typeof(ParentSagaCompleted)], _topics[typeof(ParentSagaCompleted)])); - builder.UseMessageNamingPolicy(() => - new QueueReferences(_topics[typeof(StartChildSaga)], _topics[typeof(StartChildSaga)])); - builder.UseMessageNamingPolicy(() => - new QueueReferences(_topics[typeof(ProcessChildSaga)], _topics[typeof(ProcessChildSaga)])); - builder.UseMessageNamingPolicy(() => - new QueueReferences(_topics[typeof(ChildSagaCompleted)], _topics[typeof(ChildSagaCompleted)])); - }) - .UseInMemoryPersistence(); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseAzureServiceBusTransport(); - - public Task InitializeAsync() => Task.CompletedTask; - - public async Task DisposeAsync() - { - var adminClient = new ServiceBusAdministrationClient(_fixture.Configuration.ConnectionString); - foreach(var topicName in _topics.Values) - await adminClient.DeleteTopicAsync(topicName); - } - } -} diff --git a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/E2E/ServiceBusSimpleSagaScenario.cs b/tests/OpenSleigh.Transport.AzureServiceBus.Tests/E2E/ServiceBusSimpleSagaScenario.cs deleted file mode 100644 index bc2c148f..00000000 --- a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/E2E/ServiceBusSimpleSagaScenario.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Threading.Tasks; -using Azure.Messaging.ServiceBus.Administration; -using Microsoft.Extensions.Hosting; -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Core.Tests.Sagas; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Transport.AzureServiceBus.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Transport.AzureServiceBus.Tests.E2E -{ - public class ServiceBusSimpleSagaScenario : SimpleSagaScenario, - IClassFixture, - IAsyncLifetime - { - private readonly ServiceBusFixture _fixture; - private readonly string _topicName; - private readonly string _subscriptionName; - - public ServiceBusSimpleSagaScenario(ServiceBusFixture fixture) - { - _fixture = fixture; - - var messageName = nameof(StartSimpleSaga).ToLower(); - _topicName = $"{messageName}.tests.{Guid.NewGuid()}"; - _subscriptionName = $"{messageName}.workers"; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - cfg.UseAzureServiceBusTransport(_fixture.Configuration, builder => - { - builder.UseMessageNamingPolicy(() => - new QueueReferences(_topicName, _subscriptionName)); - }) - .UseInMemoryPersistence(); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseAzureServiceBusTransport(); - - public Task InitializeAsync() => Task.CompletedTask; - - public async Task DisposeAsync() - { - var adminClient = new ServiceBusAdministrationClient(_fixture.Configuration.ConnectionString); - await adminClient.DeleteTopicAsync(_topicName); - } - } -} diff --git a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Fixtures/ServiceBusFixture.cs b/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Fixtures/ServiceBusFixture.cs index f31ea8bd..3a77c7ee 100644 --- a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Fixtures/ServiceBusFixture.cs +++ b/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Fixtures/ServiceBusFixture.cs @@ -3,7 +3,7 @@ namespace OpenSleigh.Transport.AzureServiceBus.Tests.Fixtures { - public class ServiceBusFixture + public class ServiceBusFixture : IDisposable { public ServiceBusFixture() { @@ -21,5 +21,19 @@ public ServiceBusFixture() } public AzureServiceBusConfiguration Configuration { get; init; } + + public void Dispose() + { + // uncomment this in case too many topics remain hanging + + //var adminClient = new ServiceBusAdministrationClient(this.Configuration.ConnectionString); + //var t = Task.Run(async () => + //{ + // var topics = adminClient.GetTopicsAsync(); + // await foreach (var topic in topics) + // await adminClient.DeleteTopicAsync(topic.Name); + //}); + //t.Wait(); + } } } \ No newline at end of file diff --git a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Unit/MessageParserTests.cs b/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Unit/MessageParserTests.cs index b3fc0041..052d0a23 100644 --- a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Unit/MessageParserTests.cs +++ b/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Unit/MessageParserTests.cs @@ -1,6 +1,5 @@ using System; using System.Text; -using Azure.Messaging.ServiceBus; using FluentAssertions; using NSubstitute; using OpenSleigh.Core.Utils; diff --git a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Unit/ServiceBusProcessorFactoryTests.cs b/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Unit/ServiceBusProcessorFactoryTests.cs deleted file mode 100644 index 2d1d62b1..00000000 --- a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Unit/ServiceBusProcessorFactoryTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Threading.Tasks; -using Azure.Messaging.ServiceBus; -using FluentAssertions; -using NSubstitute; -using NSubstitute.ReturnsExtensions; -using Xunit; - -namespace OpenSleigh.Transport.AzureServiceBus.Tests.Unit -{ - public class ServiceBusProcessorFactoryTests - { - [Fact] - public void ctor_should_throw_when_argument_null() - { - var serviceBusClient = NSubstitute.Substitute.ForPartsOf(); - var factory = NSubstitute.Substitute.For(); - Assert.Throws(() => new ServiceBusProcessorFactory(null, serviceBusClient)); - Assert.Throws(() => new ServiceBusProcessorFactory(factory, null)); - } - - [Fact] - public void Create_should_return_Processor() - { - var serviceBusClient = NSubstitute.Substitute.ForPartsOf(); - var processor = NSubstitute.Substitute.ForPartsOf(); - serviceBusClient.WhenForAnyArgs(c => c.CreateProcessor(Arg.Any(), Arg.Any())) - .DoNotCallBase(); - serviceBusClient.CreateProcessor(Arg.Any(), Arg.Any()) - .ReturnsForAnyArgs(processor); - - var factory = NSubstitute.Substitute.For(); - var references = new QueueReferences("lorem", "ipsum"); - factory.Create() - .Returns(references); - - var sut = new ServiceBusProcessorFactory(factory, serviceBusClient); - var result = sut.Create(); - result.Should().Be(processor); - - serviceBusClient.Received(1) - .CreateProcessor(references.TopicName, references.SubscriptionName); - } - - [Fact] - public void Create_should_recreate_Processor_when_null() - { - var serviceBusClient = NSubstitute.Substitute.ForPartsOf(); - - serviceBusClient.WhenForAnyArgs(c => c.CreateProcessor(Arg.Any(), Arg.Any())) - .DoNotCallBase(); - serviceBusClient.CreateProcessor(Arg.Any(), Arg.Any()) - .ReturnsNullForAnyArgs(); - - var factory = NSubstitute.Substitute.For(); - var references = new QueueReferences("lorem", "ipsum"); - factory.Create() - .Returns(references); - - var sut = new ServiceBusProcessorFactory(factory, serviceBusClient); - sut.Create(); - - serviceBusClient.Received(2) - .CreateProcessor(references.TopicName, references.SubscriptionName); - } - } -} diff --git a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Unit/ServiceBusSenderFactoryTests.cs b/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Unit/ServiceBusSenderFactoryTests.cs index d5f4cf5c..fceeb858 100644 --- a/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Unit/ServiceBusSenderFactoryTests.cs +++ b/tests/OpenSleigh.Transport.AzureServiceBus.Tests/Unit/ServiceBusSenderFactoryTests.cs @@ -1,5 +1,4 @@ using System; -using System.Threading.Tasks; using Azure.Messaging.ServiceBus; using FluentAssertions; using NSubstitute; diff --git a/tests/OpenSleigh.Transport.Kafka.Tests/E2E/KafkaEventBroadcastingScenario.cs b/tests/OpenSleigh.Transport.Kafka.Tests/E2E/KafkaEventBroadcastingScenario.cs deleted file mode 100644 index b46b3612..00000000 --- a/tests/OpenSleigh.Transport.Kafka.Tests/E2E/KafkaEventBroadcastingScenario.cs +++ /dev/null @@ -1,27 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Transport.Kafka.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Transport.Kafka.Tests.E2E -{ - public class KafkaEventBroadcastingScenario : EventBroadcastingScenario, IClassFixture - { - private readonly KafkaFixture _fixture; - - public KafkaEventBroadcastingScenario(KafkaFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - cfg.UseKafkaTransport(_fixture.KafkaConfiguration) - .UseInMemoryPersistence(); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseKafkaTransport(); - } -} \ No newline at end of file diff --git a/tests/OpenSleigh.Transport.Kafka.Tests/E2E/KafkaParentChildScenario.cs b/tests/OpenSleigh.Transport.Kafka.Tests/E2E/KafkaParentChildScenario.cs deleted file mode 100644 index f15f6698..00000000 --- a/tests/OpenSleigh.Transport.Kafka.Tests/E2E/KafkaParentChildScenario.cs +++ /dev/null @@ -1,27 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Transport.Kafka.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Transport.Kafka.Tests.E2E -{ - public class KafkaParentChildScenario : ParentChildScenario, IClassFixture - { - private readonly KafkaFixture _fixture; - - public KafkaParentChildScenario(KafkaFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - cfg.UseKafkaTransport(_fixture.KafkaConfiguration) - .UseInMemoryPersistence(); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseKafkaTransport(); - } -} diff --git a/tests/OpenSleigh.Transport.Kafka.Tests/E2E/KafkaSimpleSagaScenario.cs b/tests/OpenSleigh.Transport.Kafka.Tests/E2E/KafkaSimpleSagaScenario.cs deleted file mode 100644 index cb151798..00000000 --- a/tests/OpenSleigh.Transport.Kafka.Tests/E2E/KafkaSimpleSagaScenario.cs +++ /dev/null @@ -1,27 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Transport.Kafka.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Transport.Kafka.Tests.E2E -{ - public class KafkaSimpleSagaScenario : SimpleSagaScenario, IClassFixture - { - private readonly KafkaFixture _fixture; - - public KafkaSimpleSagaScenario(KafkaFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - cfg.UseKafkaTransport(_fixture.KafkaConfiguration) - .UseInMemoryPersistence(); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseKafkaTransport(); - } -} diff --git a/tests/OpenSleigh.Transport.Kafka.Tests/Unit/KafkaPublisherExecutorTests.cs b/tests/OpenSleigh.Transport.Kafka.Tests/Unit/KafkaPublisherExecutorTests.cs index 46a91e5b..5bd06ef4 100644 --- a/tests/OpenSleigh.Transport.Kafka.Tests/Unit/KafkaPublisherExecutorTests.cs +++ b/tests/OpenSleigh.Transport.Kafka.Tests/Unit/KafkaPublisherExecutorTests.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Confluent.Kafka; +using Microsoft.Extensions.Logging; using NSubstitute; using OpenSleigh.Core.Utils; using Xunit; @@ -17,9 +18,11 @@ public void ctor_should_throw_when_input_invalid() { var producer = NSubstitute.Substitute.For>(); var serializer = NSubstitute.Substitute.For(); + var logger = NSubstitute.Substitute.For>(); - Assert.Throws(() => new KafkaPublisherExecutor(null, serializer)); - Assert.Throws(() => new KafkaPublisherExecutor(producer, null)); + Assert.Throws(() => new KafkaPublisherExecutor(null, serializer, logger)); + Assert.Throws(() => new KafkaPublisherExecutor(producer, null, logger)); + Assert.Throws(() => new KafkaPublisherExecutor(producer, serializer, null)); } [Fact] @@ -27,8 +30,9 @@ public async Task PublishAsync_should_throw_when_input_invalid() { var producer = NSubstitute.Substitute.For>(); var serializer = NSubstitute.Substitute.For(); + var logger = NSubstitute.Substitute.For>(); - var sut = new KafkaPublisherExecutor(producer, serializer); + var sut = new KafkaPublisherExecutor(producer, serializer, logger); await Assert.ThrowsAsync(async () => await sut.PublishAsync(null, "lorem")); await Assert.ThrowsAsync(async () => await sut.PublishAsync(DummyMessage.New(), null)); await Assert.ThrowsAsync(async () => await sut.PublishAsync(DummyMessage.New(), "")); @@ -51,8 +55,9 @@ public async Task PublishAsync_should_publish_message() .Returns(producerResult); var serializer = NSubstitute.Substitute.For(); + var logger = NSubstitute.Substitute.For>(); - var sut = new KafkaPublisherExecutor(producer, serializer); + var sut = new KafkaPublisherExecutor(producer, serializer, logger); await sut.PublishAsync(message, topicName); @@ -79,8 +84,9 @@ public async Task PublishAsync_should_include_additional_headers_when_provided() .Returns(producerResult); var serializer = NSubstitute.Substitute.For(); + var logger = NSubstitute.Substitute.For>(); - var sut = new KafkaPublisherExecutor(producer, serializer); + var sut = new KafkaPublisherExecutor(producer, serializer, logger); var headers = new[] { diff --git a/tests/OpenSleigh.Transport.Kafka.Tests/Unit/KafkaPublisherTests.cs b/tests/OpenSleigh.Transport.Kafka.Tests/Unit/KafkaPublisherTests.cs index d4ef2207..db16680b 100644 --- a/tests/OpenSleigh.Transport.Kafka.Tests/Unit/KafkaPublisherTests.cs +++ b/tests/OpenSleigh.Transport.Kafka.Tests/Unit/KafkaPublisherTests.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Confluent.Kafka; using NSubstitute; -using NSubstitute.Core; using Xunit; namespace OpenSleigh.Transport.Kafka.Tests.Unit diff --git a/tests/OpenSleigh.Transport.Kafka.Tests/Unit/KafkaSubscriberTests.cs b/tests/OpenSleigh.Transport.Kafka.Tests/Unit/KafkaSubscriberTests.cs index 03351a6f..2ae3833e 100644 --- a/tests/OpenSleigh.Transport.Kafka.Tests/Unit/KafkaSubscriberTests.cs +++ b/tests/OpenSleigh.Transport.Kafka.Tests/Unit/KafkaSubscriberTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Confluent.Kafka; diff --git a/tests/OpenSleigh.Transport.RabbitMQ.Tests/E2E/RabbitEventBroadcastingScenario.cs b/tests/OpenSleigh.Transport.RabbitMQ.Tests/E2E/RabbitEventBroadcastingScenario.cs deleted file mode 100644 index cea325e9..00000000 --- a/tests/OpenSleigh.Transport.RabbitMQ.Tests/E2E/RabbitEventBroadcastingScenario.cs +++ /dev/null @@ -1,27 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Transport.RabbitMQ.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Transport.RabbitMQ.Tests.E2E -{ - public class RabbitEventBroadcastingScenario : EventBroadcastingScenario, IClassFixture - { - private readonly RabbitFixture _fixture; - - public RabbitEventBroadcastingScenario(RabbitFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - cfg.UseRabbitMQTransport(_fixture.RabbitConfiguration) - .UseInMemoryPersistence(); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseRabbitMQTransport(); - } -} \ No newline at end of file diff --git a/tests/OpenSleigh.Transport.RabbitMQ.Tests/E2E/RabbitParentChildScenario.cs b/tests/OpenSleigh.Transport.RabbitMQ.Tests/E2E/RabbitParentChildScenario.cs deleted file mode 100644 index 205ddbfd..00000000 --- a/tests/OpenSleigh.Transport.RabbitMQ.Tests/E2E/RabbitParentChildScenario.cs +++ /dev/null @@ -1,27 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Transport.RabbitMQ.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Transport.RabbitMQ.Tests.E2E -{ - public class RabbitParentChildScenario : ParentChildScenario, IClassFixture - { - private readonly RabbitFixture _fixture; - - public RabbitParentChildScenario(RabbitFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - cfg.UseRabbitMQTransport(_fixture.RabbitConfiguration) - .UseInMemoryPersistence(); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg)=> - cfg.UseRabbitMQTransport(); - } -} diff --git a/tests/OpenSleigh.Transport.RabbitMQ.Tests/E2E/RabbitSimpleSagaScenario.cs b/tests/OpenSleigh.Transport.RabbitMQ.Tests/E2E/RabbitSimpleSagaScenario.cs deleted file mode 100644 index 227a796b..00000000 --- a/tests/OpenSleigh.Transport.RabbitMQ.Tests/E2E/RabbitSimpleSagaScenario.cs +++ /dev/null @@ -1,27 +0,0 @@ -using OpenSleigh.Core.DependencyInjection; -using OpenSleigh.Core.Tests.E2E; -using OpenSleigh.Persistence.InMemory; -using OpenSleigh.Transport.RabbitMQ.Tests.Fixtures; -using Xunit; - -namespace OpenSleigh.Transport.RabbitMQ.Tests.E2E -{ - public class RabbitSimpleSagaScenario : SimpleSagaScenario, IClassFixture - { - private readonly RabbitFixture _fixture; - - public RabbitSimpleSagaScenario(RabbitFixture fixture) - { - _fixture = fixture; - } - - protected override void ConfigureTransportAndPersistence(IBusConfigurator cfg) - { - cfg.UseRabbitMQTransport(_fixture.RabbitConfiguration) - .UseInMemoryPersistence(); - } - - protected override void ConfigureSagaTransport(ISagaConfigurator cfg) => - cfg.UseRabbitMQTransport(); - } -} diff --git a/tests/OpenSleigh.Transport.RabbitMQ.Tests/Fixtures/RabbitFixture.cs b/tests/OpenSleigh.Transport.RabbitMQ.Tests/Fixtures/RabbitFixture.cs index 9ef37767..86b4eb4c 100644 --- a/tests/OpenSleigh.Transport.RabbitMQ.Tests/Fixtures/RabbitFixture.cs +++ b/tests/OpenSleigh.Transport.RabbitMQ.Tests/Fixtures/RabbitFixture.cs @@ -2,7 +2,7 @@ namespace OpenSleigh.Transport.RabbitMQ.Tests.Fixtures { - public class RabbitFixture + public class RabbitFixture { public RabbitFixture() { diff --git a/tests/OpenSleigh.Transport.RabbitMQ.Tests/OpenSleigh.Transport.RabbitMQ.Tests.csproj b/tests/OpenSleigh.Transport.RabbitMQ.Tests/OpenSleigh.Transport.RabbitMQ.Tests.csproj index 0fc2ee1d..7af56818 100644 --- a/tests/OpenSleigh.Transport.RabbitMQ.Tests/OpenSleigh.Transport.RabbitMQ.Tests.csproj +++ b/tests/OpenSleigh.Transport.RabbitMQ.Tests/OpenSleigh.Transport.RabbitMQ.Tests.csproj @@ -35,6 +35,7 @@ +