diff --git a/.github/workflows/pull-request-docs.yml b/.github/workflows/pull-request-docs.yml index a28ed530d..420b55e3a 100644 --- a/.github/workflows/pull-request-docs.yml +++ b/.github/workflows/pull-request-docs.yml @@ -9,6 +9,11 @@ jobs: docs: runs-on: ubuntu-latest steps: + - + name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 - name: Checkout uses: actions/checkout@v4 @@ -16,5 +21,5 @@ jobs: name: Build docs run: | cd docs - yarn install - yarn build + pnpm install + pnpm build diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 29a7d81da..91de2b1d8 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -18,9 +18,6 @@ jobs: build-and-test: name: "Build and test" runs-on: ubuntu-latest - strategy: - matrix: - dotnet: [ 'net6.0', 'net8.0' ] env: NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages TC_CLOUD_TOKEN: ${{ secrets.TC_TOKEN }} @@ -32,11 +29,14 @@ jobs: name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0' + dotnet-version: | + 8.0.x + 9.0.x + dotnet-quality: 'preview' - name: Build run: | - dotnet build -c "Debug CI" -f ${{ matrix.dotnet }} + dotnet build -c "Debug CI" - name: Prepare Testcontainers Cloud agent if: env.TC_CLOUD_TOKEN != '' @@ -44,7 +44,7 @@ jobs: - name: Run tests run: | - dotnet test -c "Debug CI" --no-build -f ${{ matrix.dotnet }} + dotnet test -c "Debug CI" --no-build - name: Upload Test Results if: always() diff --git a/Directory.Packages.props b/Directory.Packages.props index af2b1b4dd..064af7c8d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,104 +1,106 @@ - - true - - - 8.0 - 8.0.6 - - - [6.0.5,7) - 2.3.0 - - - 8.0.6 - 3.0.0 - - - 3.9.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + true + + + 9.0.0-rc.2.24474.3 + 9.0.0-rc.2.24473.5 + 9.0.0-rc.2.24474.3 + + + 8.0.6 + 8.0 + 8.0.8 + + + 3.10.0 + + + 8.0.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Eventuous.sln b/Eventuous.sln index 634def5f0..3668bfbb7 100644 --- a/Eventuous.sln +++ b/Eventuous.sln @@ -163,8 +163,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eventuous.Diagnostics", "sr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eventuous.Application", "src\Core\src\Eventuous.Application\Eventuous.Application.csproj", "{3743969A-4635-40DE-B45C-46F80CBB5733}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eventuous.Tests.Diagnostics", "src\Core\test\Eventuous.Tests.Diagnostics\Eventuous.Tests.Diagnostics.csproj", "{EAB7C8CC-FD8D-437B-ADB5-FA02FC62AAF9}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eventuous.Tests.Application", "src\Core\test\Eventuous.Tests.Application\Eventuous.Tests.Application.csproj", "{2C44A145-81E5-4F43-9C9A-AB3CF822AF82}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eventuous.Redis", "src\Redis\src\Eventuous.Redis\Eventuous.Redis.csproj", "{179C730A-CD2E-4B4D-935B-38F7E9CFE33B}" @@ -213,10 +211,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bookings.Payments", "sample EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bookings", "samples\esdb\Bookings\Bookings.csproj", "{C666D8E7-FB55-4435-A8D4-CF9815660E85}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eventuous.Tests.Persistence", "src\Core\test\Eventuous.Tests.Persistence\Eventuous.Tests.Persistence.csproj", "{F24F066B-FC7A-4298-B007-19CC86BB31E1}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Brokers", "Brokers", "{86D92758-EBB6-4B8C-94B7-BD91AF1E31D2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eventuous.Extensions.Logging", "src\Extensions\src\Eventuous.Extensions.Logging\Eventuous.Extensions.Logging.csproj", "{E1F6CDD8-D37E-487B-A429-25A06C590FE4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eventuous.TestHelpers.TUnit", "test\Eventuous.TestHelpers.TUnit\Eventuous.TestHelpers.TUnit.csproj", "{2A816CFD-5D05-4F64-8222-F7214B229EBC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -474,12 +474,6 @@ Global {3743969A-4635-40DE-B45C-46F80CBB5733}.Release|Any CPU.Build.0 = Release|Any CPU {3743969A-4635-40DE-B45C-46F80CBB5733}.Debug CI|Any CPU.ActiveCfg = Debug CI|Any CPU {3743969A-4635-40DE-B45C-46F80CBB5733}.Debug CI|Any CPU.Build.0 = Debug CI|Any CPU - {EAB7C8CC-FD8D-437B-ADB5-FA02FC62AAF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EAB7C8CC-FD8D-437B-ADB5-FA02FC62AAF9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EAB7C8CC-FD8D-437B-ADB5-FA02FC62AAF9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EAB7C8CC-FD8D-437B-ADB5-FA02FC62AAF9}.Release|Any CPU.Build.0 = Release|Any CPU - {EAB7C8CC-FD8D-437B-ADB5-FA02FC62AAF9}.Debug CI|Any CPU.ActiveCfg = Debug CI|Any CPU - {EAB7C8CC-FD8D-437B-ADB5-FA02FC62AAF9}.Debug CI|Any CPU.Build.0 = Debug CI|Any CPU {2C44A145-81E5-4F43-9C9A-AB3CF822AF82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2C44A145-81E5-4F43-9C9A-AB3CF822AF82}.Debug|Any CPU.Build.0 = Debug|Any CPU {2C44A145-81E5-4F43-9C9A-AB3CF822AF82}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -552,12 +546,18 @@ Global {C666D8E7-FB55-4435-A8D4-CF9815660E85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C666D8E7-FB55-4435-A8D4-CF9815660E85}.Debug|Any CPU.Build.0 = Debug|Any CPU {C666D8E7-FB55-4435-A8D4-CF9815660E85}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F24F066B-FC7A-4298-B007-19CC86BB31E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F24F066B-FC7A-4298-B007-19CC86BB31E1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F24F066B-FC7A-4298-B007-19CC86BB31E1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F24F066B-FC7A-4298-B007-19CC86BB31E1}.Release|Any CPU.Build.0 = Release|Any CPU - {F24F066B-FC7A-4298-B007-19CC86BB31E1}.Debug CI|Any CPU.ActiveCfg = Debug CI|Any CPU - {F24F066B-FC7A-4298-B007-19CC86BB31E1}.Debug CI|Any CPU.Build.0 = Debug CI|Any CPU + {E1F6CDD8-D37E-487B-A429-25A06C590FE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1F6CDD8-D37E-487B-A429-25A06C590FE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1F6CDD8-D37E-487B-A429-25A06C590FE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1F6CDD8-D37E-487B-A429-25A06C590FE4}.Release|Any CPU.Build.0 = Release|Any CPU + {E1F6CDD8-D37E-487B-A429-25A06C590FE4}.Debug CI|Any CPU.ActiveCfg = Debug CI|Any CPU + {E1F6CDD8-D37E-487B-A429-25A06C590FE4}.Debug CI|Any CPU.Build.0 = Debug CI|Any CPU + {2A816CFD-5D05-4F64-8222-F7214B229EBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A816CFD-5D05-4F64-8222-F7214B229EBC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A816CFD-5D05-4F64-8222-F7214B229EBC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A816CFD-5D05-4F64-8222-F7214B229EBC}.Release|Any CPU.Build.0 = Release|Any CPU + {2A816CFD-5D05-4F64-8222-F7214B229EBC}.Debug CI|Any CPU.ActiveCfg = Debug CI|Any CPU + {2A816CFD-5D05-4F64-8222-F7214B229EBC}.Debug CI|Any CPU.Build.0 = Debug CI|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -629,7 +629,6 @@ Global {8B155B20-B23E-490E-85B8-9712BCA91F04} = {7BBD9F8E-EB6A-4E2D-84A9-AF15C1784401} {A25D6B10-5B40-46B2-ACBB-F47C76ECFAB8} = {7BBD9F8E-EB6A-4E2D-84A9-AF15C1784401} {3743969A-4635-40DE-B45C-46F80CBB5733} = {7BBD9F8E-EB6A-4E2D-84A9-AF15C1784401} - {EAB7C8CC-FD8D-437B-ADB5-FA02FC62AAF9} = {0ED6785B-60EF-46B4-B938-EF04189FC8BC} {2C44A145-81E5-4F43-9C9A-AB3CF822AF82} = {0ED6785B-60EF-46B4-B938-EF04189FC8BC} {179C730A-CD2E-4B4D-935B-38F7E9CFE33B} = {0E2520E7-B4A6-47E7-AED8-662C88441A84} {5DACCAF8-9CD4-4B0B-96B7-15EF049EB199} = {B1AAD6CA-2710-41E0-9495-B43C313D6BCA} @@ -653,10 +652,11 @@ Global {7D86A33D-7C1A-45F7-BEFF-1B95525716D6} = {75F337AF-7E15-4ED1-8E4F-A582DABEA373} {7D24DAB3-FD49-443C-811A-96F0CA6A6F9A} = {75F337AF-7E15-4ED1-8E4F-A582DABEA373} {C666D8E7-FB55-4435-A8D4-CF9815660E85} = {75F337AF-7E15-4ED1-8E4F-A582DABEA373} - {F24F066B-FC7A-4298-B007-19CC86BB31E1} = {0ED6785B-60EF-46B4-B938-EF04189FC8BC} {1970CA0D-C5E8-4384-8485-82D712289002} = {86D92758-EBB6-4B8C-94B7-BD91AF1E31D2} {6E545DFE-FE70-4486-92E0-E47E86E66210} = {86D92758-EBB6-4B8C-94B7-BD91AF1E31D2} {2E59C5F8-3E5A-4450-B902-7648AD7ECC0F} = {86D92758-EBB6-4B8C-94B7-BD91AF1E31D2} + {E1F6CDD8-D37E-487B-A429-25A06C590FE4} = {2B7F84B7-C0E5-408F-ABAF-BF23C8305486} + {2A816CFD-5D05-4F64-8222-F7214B229EBC} = {C60C6094-2A03-45B6-AB33-C514C35DF823} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0691467B-C257-46DB-BC4F-88EB7CD615B8} diff --git a/docs/package.json b/docs/package.json index 90c9baaca..2869ffdee 100644 --- a/docs/package.json +++ b/docs/package.json @@ -46,7 +46,7 @@ ] }, "engines": { - "node": ">=16.14" + "node": ">=18.19.0" }, "packageManager": "pnpm@9.10.0" } diff --git a/docs/versioned_docs/version-0.15/application/command-api.md b/docs/versioned_docs/version-0.15/application/command-api.md index 921bc2ba9..6331f6a6e 100644 --- a/docs/versioned_docs/version-0.15/application/command-api.md +++ b/docs/versioned_docs/version-0.15/application/command-api.md @@ -24,7 +24,7 @@ Here is an example of a command API controller: ```csharp [Route("/booking")] -public class CommandApi(ICommandService service) +public class CommandApi(ICommandService service) : CommandHttpApiBase { [HttpPost] [Route("book")] diff --git a/props/Common.props b/props/Common.props new file mode 100644 index 000000000..2d45cd706 --- /dev/null +++ b/props/Common.props @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index 53ba4c935..da955ecae 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -10,4 +10,5 @@ $(RepoRoot)\src Debug;Release + \ No newline at end of file diff --git a/samples/esdb/Bookings.Payments/Bookings.Payments.csproj b/samples/esdb/Bookings.Payments/Bookings.Payments.csproj index c3758e9f8..aa9f8bed0 100644 --- a/samples/esdb/Bookings.Payments/Bookings.Payments.csproj +++ b/samples/esdb/Bookings.Payments/Bookings.Payments.csproj @@ -1,32 +1,32 @@ - - Debug;Release - - - - - - - - - - - - - - - - true - PreserveNewest - PreserveNewest - - - - - - - - - - + + Debug;Release + + + + + + + + + + + + + + + + true + PreserveNewest + PreserveNewest + + + + + + + + + + \ No newline at end of file diff --git a/samples/esdb/Bookings.Payments/Program.cs b/samples/esdb/Bookings.Payments/Program.cs index e8075f99e..f2679abe2 100644 --- a/samples/esdb/Bookings.Payments/Program.cs +++ b/samples/esdb/Bookings.Payments/Program.cs @@ -19,7 +19,7 @@ builder.Host.UseSerilog(); var app = builder.Build(); -app.UseEventuousLogs(); +app.Services.AddEventuousLogs(); app.UseSwagger(); app.UseOpenTelemetryPrometheusScrapingEndpoint(); diff --git a/samples/esdb/Bookings/Bookings.csproj b/samples/esdb/Bookings/Bookings.csproj index b77713ee3..1aaebe78f 100644 --- a/samples/esdb/Bookings/Bookings.csproj +++ b/samples/esdb/Bookings/Bookings.csproj @@ -1,39 +1,39 @@ - - Linux - Debug;Release - AnyCPU - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - PreserveNewest - - + + Linux + Debug;Release + AnyCPU + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + PreserveNewest + + \ No newline at end of file diff --git a/samples/esdb/Bookings/Registrations.cs b/samples/esdb/Bookings/Registrations.cs index 3c4f79f46..35f524952 100644 --- a/samples/esdb/Bookings/Registrations.cs +++ b/samples/esdb/Bookings/Registrations.cs @@ -11,6 +11,7 @@ using Eventuous.EventStore.Subscriptions; using Eventuous.Projections.MongoDB; using Eventuous.Subscriptions.Registrations; +using MongoDB.Driver.Core.Extensions.DiagnosticSources; using NodaTime; using NodaTime.Serialization.SystemTextJson; using OpenTelemetry.Metrics; @@ -76,7 +77,7 @@ public static void AddTelemetry(this IServiceCollection services) { .AddAspNetCoreInstrumentation() .AddGrpcClientInstrumentation() .AddEventuousTracing() - .AddMongoDBInstrumentation() + .AddSource(typeof(DiagnosticsActivityEventSubscriber).Assembly.GetName().Name!) .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("bookings")) .SetSampler(new AlwaysOnSampler()); diff --git a/samples/postgres/Bookings.Payments/Bookings.Payments.csproj b/samples/postgres/Bookings.Payments/Bookings.Payments.csproj index b19b7048b..2447ff1b2 100644 --- a/samples/postgres/Bookings.Payments/Bookings.Payments.csproj +++ b/samples/postgres/Bookings.Payments/Bookings.Payments.csproj @@ -5,7 +5,8 @@ - + + @@ -33,7 +34,7 @@ - + diff --git a/samples/postgres/Bookings.Payments/Program.cs b/samples/postgres/Bookings.Payments/Program.cs index 4b0e16263..92c29e128 100644 --- a/samples/postgres/Bookings.Payments/Program.cs +++ b/samples/postgres/Bookings.Payments/Program.cs @@ -20,7 +20,7 @@ var app = builder.Build(); -app.UseEventuousLogs(); +app.Services.AddEventuousLogs(); app.UseSwagger().UseSwaggerUI(); app.UseOpenTelemetryPrometheusScrapingEndpoint(); diff --git a/samples/postgres/Bookings/Bookings.csproj b/samples/postgres/Bookings/Bookings.csproj index ec6a13e80..413f971ad 100644 --- a/samples/postgres/Bookings/Bookings.csproj +++ b/samples/postgres/Bookings/Bookings.csproj @@ -1,34 +1,34 @@ - - Linux - Debug;Release - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + Linux + Debug;Release + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/postgres/Bookings/Infrastructure/Mongo.cs b/samples/postgres/Bookings/Infrastructure/Mongo.cs index d10633e1c..400375d6d 100644 --- a/samples/postgres/Bookings/Infrastructure/Mongo.cs +++ b/samples/postgres/Bookings/Infrastructure/Mongo.cs @@ -12,14 +12,11 @@ public static IMongoDatabase ConfigureMongo(IConfiguration configuration) { var settings = MongoClientSettings.FromConnectionString(config.ConnectionString); if (config is { User: not null, Password: not null }) { - settings.Credential = new MongoCredential( - null, - new MongoInternalIdentity("admin", config.User), - new PasswordEvidence(config.Password) - ); + settings.Credential = new(null, new MongoInternalIdentity("admin", config.User), new PasswordEvidence(config.Password)); } settings.ClusterConfigurator = cb => cb.Subscribe(new DiagnosticsActivityEventSubscriber()); + return new MongoClient(settings).GetDatabase(config.Database); } diff --git a/samples/postgres/Bookings/Infrastructure/Telemetry.cs b/samples/postgres/Bookings/Infrastructure/Telemetry.cs index 02b4ec22f..d2069283f 100644 --- a/samples/postgres/Bookings/Infrastructure/Telemetry.cs +++ b/samples/postgres/Bookings/Infrastructure/Telemetry.cs @@ -1,4 +1,5 @@ using Eventuous.Diagnostics.OpenTelemetry; +using MongoDB.Driver.Core.Extensions.DiagnosticSources; using Npgsql; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; @@ -30,7 +31,7 @@ public static void AddTelemetry(this IServiceCollection services) { .AddAspNetCoreInstrumentation() .AddEventuousTracing() .AddNpgsql() - .AddMongoDBInstrumentation(); + .AddSource(typeof(DiagnosticsActivityEventSubscriber).Assembly.GetName().Name!); if (otelEnabled) builder.AddOtlpExporter(); diff --git a/src/Core/src/Eventuous.Persistence/AggregateFactory.cs b/src/Core/src/Eventuous.Persistence/AggregateFactory.cs index 440b9549a..82a8d6909 100644 --- a/src/Core/src/Eventuous.Persistence/AggregateFactory.cs +++ b/src/Core/src/Eventuous.Persistence/AggregateFactory.cs @@ -12,6 +12,15 @@ public class AggregateFactoryRegistry { /// Aggregate factory registry singleton instance /// public static readonly AggregateFactoryRegistry Instance = new(); + + private AggregateFactoryRegistry() { } + + [UsedImplicitly] + public AggregateFactoryRegistry(IServiceProvider sp, IEnumerable resolvers) { + foreach (var resolver in resolvers) { + UnsafeCreateAggregateUsing(resolver.Type, () => resolver.CreateInstance(sp)); + } + } internal readonly Dictionary> Registry = new(); @@ -39,3 +48,5 @@ public AggregateFactoryRegistry CreateAggregateUsing(AggregateFactory } public delegate T AggregateFactory() where T : Aggregate where TState : State, new(); + +public record ResolveAggregateFactory(Type Type, Func CreateInstance); diff --git a/src/Core/src/Eventuous.Producers/Diagnostics/ProducerEventSource.cs b/src/Core/src/Eventuous.Producers/Diagnostics/ProducerEventSource.cs index 24d5404a6..ea5b5b313 100644 --- a/src/Core/src/Eventuous.Producers/Diagnostics/ProducerEventSource.cs +++ b/src/Core/src/Eventuous.Producers/Diagnostics/ProducerEventSource.cs @@ -20,7 +20,7 @@ public class ProducerEventSource : EventSource where T : class { [NonEvent] public void ProduceAcknowledged(ProducedMessage message) { if (IsEnabled(EventLevel.Verbose, EventKeywords.All)) { - ProduceAcknowledged(ProducerName, message); + ProduceAcknowledged(ProducerName, message.GetType().Name); } } @@ -29,14 +29,14 @@ public void ProduceNotAcknowledged(ProducedMessage message, string error, Except if (!IsEnabled(EventLevel.Verbose, EventKeywords.All)) return; var errorMessage = $"{error} {e?.Message}"; - ProduceNotAcknowledged(ProducerName, message, errorMessage); + ProduceNotAcknowledged(ProducerName, message.GetType().Name, errorMessage); } [Event(ProduceAcknowledgedId, Level = EventLevel.Verbose, Message = "[{0}] Produce acknowledged: {1}")] - void ProduceAcknowledged(string producer, object message) - => WriteEvent(ProduceAcknowledgedId, producer, message.GetType().Name); + void ProduceAcknowledged(string producer, string messageType) + => WriteEvent(ProduceAcknowledgedId, producer, messageType); [Event(ProduceNotAcknowledgedId, Level = EventLevel.Verbose, Message = "[{0}] Produce not acknowledged: {1} {2}")] - void ProduceNotAcknowledged(string producer, object message, string error) - => WriteEvent(ProduceNotAcknowledgedId, producer, message.GetType().Name, error); + void ProduceNotAcknowledged(string producer, string messageType, string error) + => WriteEvent(ProduceNotAcknowledgedId, producer, messageType, error); } \ No newline at end of file diff --git a/src/Core/src/Eventuous.Subscriptions/Checkpoints/CheckpointCommitHandler.cs b/src/Core/src/Eventuous.Subscriptions/Checkpoints/CheckpointCommitHandler.cs index 0fcf1500e..678356ff7 100644 --- a/src/Core/src/Eventuous.Subscriptions/Checkpoints/CheckpointCommitHandler.cs +++ b/src/Core/src/Eventuous.Subscriptions/Checkpoints/CheckpointCommitHandler.cs @@ -79,7 +79,7 @@ async ValueTask Process(CommitPosition[] list, CancellationToken cancellationTok [PublicAPI] public ValueTask Commit(CommitPosition position, CancellationToken cancellationToken) { if (Diagnostic.IsEnabled(CommitOperation)) Diagnostic.Write(CommitOperation, new CommitEvent(_subscriptionId, position, _positions.Min)); - position.LogContext.PositionReceived(position); + position.LogContext?.PositionReceived(position); return _worker.Write(position, cancellationToken); } @@ -124,7 +124,7 @@ async Task CommitInternal(CommitPosition position, bool force, CancellationToken return; } - position.LogContext.CommittingPosition(position); + position.LogContext?.CommittingPosition(position); await _commitCheckpoint(new(_subscriptionId, position.Position), force, cancellationToken).NoContext(); _lastCommit = position; _positions.RemoveWhere(x => x.Sequence <= position.Sequence); @@ -132,7 +132,7 @@ async Task CommitInternal(CommitPosition position, bool force, CancellationToken await _commitCheckpoint(new(_subscriptionId, position.Position), true, default).NoContext(); _positions.RemoveWhere(x => x.Sequence <= position.Sequence); } catch (Exception e) { - position.LogContext.UnableToCommitPosition(position, e); + position.LogContext?.UnableToCommitPosition(position, e); } } @@ -149,7 +149,7 @@ public async ValueTask DisposeAsync() { public readonly record struct CommitPosition(ulong Position, ulong Sequence, DateTime Timestamp) { public bool Valid { get; private init; } = true; - public LogContext LogContext { get; init; } + public LogContext? LogContext { get; init; } public static readonly CommitPosition None = new(0, 0, DateTime.MinValue) { Valid = false }; diff --git a/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilderExtensions.cs b/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilderExtensions.cs index a9e346d2f..8667b5000 100644 --- a/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilderExtensions.cs +++ b/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilderExtensions.cs @@ -42,8 +42,7 @@ public static SubscriptionBuilder WithPartitioningByStream(this SubscriptionBuil /// Subscription options type /// Checkpoint store type /// - public static SubscriptionBuilder UseCheckpointStore - (this SubscriptionBuilder builder) + public static SubscriptionBuilder UseCheckpointStore(this SubscriptionBuilder builder) where T : class, ICheckpointStore where TSubscription : EventSubscriptionWithCheckpoint where TOptions : SubscriptionWithCheckpointOptions { diff --git a/src/Core/test/Eventuous.Tests.Application/CommandServiceTests.cs b/src/Core/test/Eventuous.Tests.Application/CommandServiceTests.cs index 6b37ced91..a6c3dfe7e 100644 --- a/src/Core/test/Eventuous.Tests.Application/CommandServiceTests.cs +++ b/src/Core/test/Eventuous.Tests.Application/CommandServiceTests.cs @@ -4,7 +4,8 @@ namespace Eventuous.Tests.Application; // ReSharper disable once UnusedType.Global -public class CommandServiceTests(ITestOutputHelper output) : ServiceTestBase(output) { +[InheritsTests] +public class CommandServiceTests : ServiceTestBase { protected override ICommandService CreateService( AmendEvent? amendEvent = null, AmendEvent? amendAll = null diff --git a/src/Core/test/Eventuous.Tests.Application/Eventuous.Tests.Application.csproj b/src/Core/test/Eventuous.Tests.Application/Eventuous.Tests.Application.csproj index c46c553b7..024226d4a 100644 --- a/src/Core/test/Eventuous.Tests.Application/Eventuous.Tests.Application.csproj +++ b/src/Core/test/Eventuous.Tests.Application/Eventuous.Tests.Application.csproj @@ -2,13 +2,25 @@ true true + Exe + true + ServiceTestBase.cs + + ServiceTestBase.cs + + + ServiceTestBase.cs + + + ServiceTestBase.cs + diff --git a/src/Core/test/Eventuous.Tests.Application/FunctionalServiceTests.cs b/src/Core/test/Eventuous.Tests.Application/FunctionalServiceTests.cs index 7462f9b81..df45d3021 100644 --- a/src/Core/test/Eventuous.Tests.Application/FunctionalServiceTests.cs +++ b/src/Core/test/Eventuous.Tests.Application/FunctionalServiceTests.cs @@ -5,7 +5,8 @@ namespace Eventuous.Tests.Application; using Sut.Domain; // ReSharper disable once UnusedType.Global -public class FunctionalServiceTests(ITestOutputHelper output) : ServiceTestBase(output) { +[InheritsTests] +public class FunctionalServiceTests() : ServiceTestBase() { protected override ICommandService CreateService( AmendEvent? amendEvent = null, AmendEvent? amendAll = null diff --git a/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.Amendments.cs b/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.Amendments.cs index d51bb415a..09db048a0 100644 --- a/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.Amendments.cs +++ b/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.Amendments.cs @@ -4,18 +4,18 @@ namespace Eventuous.Tests.Application; public abstract partial class ServiceTestBase { - [Fact] - public async Task Should_amend_event_from_command() { + [Test] + public async Task Should_amend_event_from_command(CancellationToken cancellationToken) { var service = CreateService(amendEvent: AmendEvent); var cmd = CreateCommand(); - await service.Handle(cmd, default); + await service.Handle(cmd, cancellationToken); - var stream = await Store.ReadStream(StreamName.For(cmd.BookingId), StreamReadPosition.Start); + var stream = await Store.ReadStream(StreamName.For(cmd.BookingId), StreamReadPosition.Start, cancellationToken: cancellationToken); stream[0].Metadata["userId"].Should().Be(cmd.ImportedBy); } - [Fact] + [Test] public async Task Should_amend_event_with_static_meta() { var cmd = Helpers.GetBookRoom(); @@ -26,14 +26,14 @@ await CommandServiceFixture .Then(x => x.StreamIs(e => e[0].Metadata["foo"].Should().Be("bar"))); } - [Fact] - public async Task Should_combine_amendments() { + [Test] + public async Task Should_combine_amendments(CancellationToken cancellationToken) { var service = CreateService(amendEvent: AmendEvent, amendAll: AddMeta); var cmd = CreateCommand(); - await service.Handle(cmd, default); + await service.Handle(cmd, cancellationToken); - var stream = await Store.ReadStream(StreamName.For(cmd.BookingId), StreamReadPosition.Start); + var stream = await Store.ReadStream(StreamName.For(cmd.BookingId), StreamReadPosition.Start, cancellationToken: cancellationToken); stream[0].Metadata["userId"].Should().Be(cmd.ImportedBy); stream[0].Metadata["foo"].Should().Be("bar"); } diff --git a/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.OnAny.cs b/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.OnAny.cs index 7c4890c45..1eac4e8ff 100644 --- a/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.OnAny.cs +++ b/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.OnAny.cs @@ -5,7 +5,7 @@ namespace Eventuous.Tests.Application; public abstract partial class ServiceTestBase { - [Fact] + [Test] public async Task Should_execute_on_any_no_stream() { var bookRoom = Helpers.GetBookRoom(); diff --git a/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.OnExisting.cs b/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.OnExisting.cs index 7a28f51c6..26aa7e8f2 100644 --- a/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.OnExisting.cs +++ b/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.OnExisting.cs @@ -6,7 +6,7 @@ namespace Eventuous.Tests.Application; public abstract partial class ServiceTestBase { - [Fact] + [Test] public async Task Should_execute_on_existing_stream_exists() { var seedCmd = Helpers.GetBookRoom(); var seed = new BookingEvents.RoomBooked(seedCmd.RoomId, seedCmd.CheckIn, seedCmd.CheckOut, seedCmd.Price); @@ -27,7 +27,7 @@ await CommandServiceFixture .Then(result => result.ResultIsOk().NewStreamEventsAre(expectedResult)); } - [Fact] + [Test] public async Task Should_fail_on_existing_no_stream() { var seedCmd = Helpers.GetBookRoom(); var paymentTime = DateTimeOffset.Now; diff --git a/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.OnNew.cs b/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.OnNew.cs index c2ae68116..3731188ac 100644 --- a/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.OnNew.cs +++ b/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.OnNew.cs @@ -4,7 +4,7 @@ namespace Eventuous.Tests.Application; public abstract partial class ServiceTestBase { - [Fact] + [Test] public async Task Should_run_on_new_no_stream() { var cmd = Helpers.GetBookRoom(); var expected = new BookingEvents.RoomBooked(cmd.RoomId, cmd.CheckIn, cmd.CheckOut, cmd.Price); @@ -16,7 +16,7 @@ await CommandServiceFixture .Then(result => result.ResultIsOk(x => x.Changes.Should().HaveCount(1)).FullStreamEventsAre(expected)); } - [Fact] + [Test] public async Task Should_fail_on_new_stream_exists() { var cmd = Helpers.GetBookRoom(); var seed = new BookingEvents.RoomBooked(cmd.RoomId, cmd.CheckIn, cmd.CheckOut, cmd.Price); diff --git a/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.cs b/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.cs index 2d2319a49..534613798 100644 --- a/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.cs +++ b/src/Core/test/Eventuous.Tests.Application/ServiceTestBase.cs @@ -1,22 +1,22 @@ using Eventuous.Sut.App; using Eventuous.Sut.Domain; -using Eventuous.TestHelpers; +using Eventuous.TestHelpers.TUnit; using Eventuous.Testing; using NodaTime; using static Eventuous.Sut.Domain.BookingEvents; namespace Eventuous.Tests.Application; -public abstract partial class ServiceTestBase : IDisposable { - [Fact] - public async Task Ensure_builder_is_thread_safe() { +public abstract partial class ServiceTestBase { + [Test] + public async Task Ensure_builder_is_thread_safe(CancellationToken cancellationToken) { const int threadCount = 3; var service = CreateService(); var tasks = Enumerable .Range(1, threadCount) - .Select(bookingId => Task.Run(() => service.Handle(Helpers.GetBookRoom(bookingId.ToString()), default))) + .Select(bookingId => Task.Run(() => service.Handle(Helpers.GetBookRoom(bookingId.ToString()), cancellationToken))) .ToList(); await Task.WhenAll(tasks); @@ -42,8 +42,8 @@ static ImportBooking CreateCommand() { return cmd; } - protected ServiceTestBase(ITestOutputHelper output) { - _listener = new(output); + protected ServiceTestBase() { + _listener = new(); TypeMap.RegisterKnownEventTypes(typeof(RoomBooked).Assembly); } @@ -66,5 +66,6 @@ protected record ImportBooking( string ImportedBy ); + [After(Test)] public void Dispose() => _listener.Dispose(); } diff --git a/src/Core/test/Eventuous.Tests.Application/StateWithIdTests.cs b/src/Core/test/Eventuous.Tests.Application/StateWithIdTests.cs index 0b55c3ad7..bd9e9028d 100644 --- a/src/Core/test/Eventuous.Tests.Application/StateWithIdTests.cs +++ b/src/Core/test/Eventuous.Tests.Application/StateWithIdTests.cs @@ -12,8 +12,8 @@ public class StateWithIdTests { public StateWithIdTests() => _service = new(_store); - [Fact] - public async Task ShouldGetIdForNew() { + [Test] + public async Task ShouldGetIdForNew(CancellationToken cancellationToken) { var map = new StreamNameMap(); var id = Guid.NewGuid().ToString(); var result = await Seed(id); @@ -24,7 +24,7 @@ public async Task ShouldGetIdForNew() { result.TryGet(out var ok).Should().BeTrue(); ok!.State.Id.Should().Be(bookingId); - var instance = await _store.LoadAggregate(bookingId, map, true); + var instance = await _store.LoadAggregate(bookingId, map, true, cancellationToken: cancellationToken); // Ensure that the id was set when the aggregate was loaded instance.State.Id.Should().Be(bookingId); diff --git a/src/Core/test/Eventuous.Tests.Diagnostics/Eventuous.Tests.Diagnostics.csproj b/src/Core/test/Eventuous.Tests.Diagnostics/Eventuous.Tests.Diagnostics.csproj deleted file mode 100644 index 1d616f398..000000000 --- a/src/Core/test/Eventuous.Tests.Diagnostics/Eventuous.Tests.Diagnostics.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - true - true - - diff --git a/src/Core/test/Eventuous.Tests.Persistence.Base/Eventuous.Tests.Persistence.Base.csproj b/src/Core/test/Eventuous.Tests.Persistence.Base/Eventuous.Tests.Persistence.Base.csproj index dc4de2531..20223ed8d 100644 --- a/src/Core/test/Eventuous.Tests.Persistence.Base/Eventuous.Tests.Persistence.Base.csproj +++ b/src/Core/test/Eventuous.Tests.Persistence.Base/Eventuous.Tests.Persistence.Base.csproj @@ -1,16 +1,22 @@  - - true - - - - - - - - - - - - + + true + false + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/DomainFixture.cs b/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/DomainFixture.cs index f68362ce3..5483f9560 100644 --- a/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/DomainFixture.cs +++ b/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/DomainFixture.cs @@ -1,10 +1,11 @@ +using AutoFixture; using Eventuous.Sut.App; using NodaTime; namespace Eventuous.Tests.Persistence.Base.Fixtures; public static class DomainFixture { - static DomainFixture() => TypeMap.RegisterKnownEventTypes(); + static DomainFixture() => TypeMap.RegisterKnownEventTypes(typeof(DomainFixture).Assembly); public static Commands.ImportBooking CreateImportBooking(IFixture auto) { var from = auto.Create(); diff --git a/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/Helpers.cs b/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/Helpers.cs index cd4e00e27..3a22e4c0a 100644 --- a/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/Helpers.cs +++ b/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/Helpers.cs @@ -1,3 +1,4 @@ +using AutoFixture; using static Eventuous.Sut.App.Commands; using static Eventuous.Sut.Domain.BookingEvents; diff --git a/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/StoreFixtureBase.cs b/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/StoreFixtureBase.cs index e380699be..2769af31b 100644 --- a/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/StoreFixtureBase.cs +++ b/src/Core/test/Eventuous.Tests.Persistence.Base/Fixtures/StoreFixtureBase.cs @@ -1,37 +1,35 @@ -// Copyright (C) Ubiquitous AS.All rights reserved -// Licensed under the Apache License, Version 2.0. - using System.Text.RegularExpressions; +using AutoFixture; using Bogus; using DotNet.Testcontainers.Containers; using Eventuous.TestHelpers; +using Eventuous.TestHelpers.TUnit.Logging; using MicroElements.AutoFixture.NodaTime; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using TUnit.Core.Interfaces; namespace Eventuous.Tests.Persistence.Base.Fixtures; +public interface IStartableFixture : IAsyncInitializer, IAsyncDisposable; + public abstract class StoreFixtureBase { - public IEventStore EventStore { get; protected private set; } = null!; - public IFixture Auto { get; } = new Fixture().Customize(new NodaTimeCustomization()); - protected static Faker Faker { get; } = new(); - protected ServiceProvider Provider { get; set; } = null!; - protected bool AutoStart { get; init; } = true; - public ITestOutputHelper? Output { get; set; } - public TypeMapper TypeMapper { get; } = new(); + public IEventStore EventStore { get; protected private set; } = null!; + public IFixture Auto { get; } = new Fixture().Customize(new NodaTimeCustomization()); + protected static Faker Faker { get; } = new(); + protected ServiceProvider Provider { get; set; } = null!; + protected bool AutoStart { get; init; } = true; + public TypeMapper TypeMapper { get; } = new(); } -public abstract partial class StoreFixtureBase : StoreFixtureBase, IAsyncLifetime where TContainer : DockerContainer { +public abstract partial class StoreFixtureBase : StoreFixtureBase, IStartableFixture where TContainer : DockerContainer { public virtual async Task InitializeAsync() { Container = CreateContainer(); await Container.StartAsync(); var services = new ServiceCollection(); - if (Output != null) { - services.AddSingleton(Output); - services.AddLogging(cfg => cfg.AddXunit(Output, LogLevel.Debug).SetMinimumLevel(LogLevel.Debug)); - } + services.AddLogging(cfg => cfg.ForTests()); Serializer = new DefaultEventSerializer(TestPrimitives.DefaultOptions, TypeMapper); services.AddSingleton(Serializer); @@ -55,7 +53,7 @@ protected async Task Start() { } } - public virtual async Task DisposeAsync() { + public virtual async ValueTask DisposeAsync() { if (_disposed) return; _disposed = true; diff --git a/src/Core/test/Eventuous.Tests.Persistence.Base/Store/Append.cs b/src/Core/test/Eventuous.Tests.Persistence.Base/Store/Append.cs index 42fd41ff3..aa353d71b 100644 --- a/src/Core/test/Eventuous.Tests.Persistence.Base/Store/Append.cs +++ b/src/Core/test/Eventuous.Tests.Persistence.Base/Store/Append.cs @@ -6,7 +6,7 @@ namespace Eventuous.Tests.Persistence.Base.Store; -public abstract class StoreAppendTests : IClassFixture where T : StoreFixtureBase { +public abstract class StoreAppendTests where T : StoreFixtureBase { readonly T _fixture; protected StoreAppendTests(T fixture) { @@ -14,18 +14,18 @@ protected StoreAppendTests(T fixture) { _fixture = fixture; } - [Fact] - [Trait("Category", "Store")] + [Test] + [Category("Store")] public async Task ShouldAppendToNoStream() { var evt = _fixture.CreateEvent(); var streamName = _fixture.GetStreamName(); var result = await _fixture.AppendEvent(streamName, evt, ExpectedStreamVersion.NoStream); - result.NextExpectedVersion.Should().Be(0); + await Assert.That(result.NextExpectedVersion).IsEqualTo(0); } - [Fact] - [Trait("Category", "Store")] + [Test] + [Category("Store")] public async Task ShouldAppendOneByOne() { var evt = _fixture.CreateEvent(); var stream = _fixture.GetStreamName(); @@ -37,11 +37,11 @@ public async Task ShouldAppendOneByOne() { var version = new ExpectedStreamVersion(result.NextExpectedVersion); result = await _fixture.AppendEvent(stream, evt, version); - result.NextExpectedVersion.Should().Be(1); + await Assert.That(result.NextExpectedVersion).IsEqualTo(1); } - [Fact] - [Trait("Category", "Store")] + [Test] + [Category("Store")] public async Task ShouldFailOnWrongVersionNoStream() { var evt = _fixture.CreateEvent(); var stream = _fixture.GetStreamName(); @@ -50,12 +50,11 @@ public async Task ShouldFailOnWrongVersionNoStream() { evt = _fixture.CreateEvent(); - var task = () => _fixture.AppendEvent(stream, evt, ExpectedStreamVersion.NoStream); - await task.Should().ThrowAsync(); + await Assert.That(() => _fixture.AppendEvent(stream, evt, ExpectedStreamVersion.NoStream)).Throws(); } - [Fact] - [Trait("Category", "Store")] + [Test] + [Category("Store")] public async Task ShouldFailOnWrongVersion() { var evt = _fixture.CreateEvent(); var stream = _fixture.GetStreamName(); @@ -64,13 +63,12 @@ public async Task ShouldFailOnWrongVersion() { evt = _fixture.CreateEvent(); - var task = () => _fixture.AppendEvent(stream, evt, new(3)); - await task.Should().ThrowAsync(); + await Assert.That(() => _fixture.AppendEvent(stream, evt, new(3))).Throws(); } - [Fact] - [Trait("Category", "Store")] + [Test] + [Category("Store")] public async Task ShouldFailOnWrongVersionWithOptimisticConcurrencyException() { var evt = _fixture.CreateEvent(); var stream = _fixture.GetStreamName(); @@ -79,7 +77,6 @@ public async Task ShouldFailOnWrongVersionWithOptimisticConcurrencyException() { evt = _fixture.CreateEvent(); - var task = () => _fixture.StoreChanges(stream, evt, new(3)); - await task.Should().ThrowAsync(); + await Assert.That(() => _fixture.StoreChanges(stream, evt, new(3))).Throws(); } } diff --git a/src/Core/test/Eventuous.Tests.Persistence.Base/Store/OtherMethods.cs b/src/Core/test/Eventuous.Tests.Persistence.Base/Store/OtherMethods.cs index 06e871685..439885867 100644 --- a/src/Core/test/Eventuous.Tests.Persistence.Base/Store/OtherMethods.cs +++ b/src/Core/test/Eventuous.Tests.Persistence.Base/Store/OtherMethods.cs @@ -3,7 +3,7 @@ namespace Eventuous.Tests.Persistence.Base.Store; -public abstract class StoreOtherOpsTests : IClassFixture where T : StoreFixtureBase { +public abstract class StoreOtherOpsTests where T : StoreFixtureBase { readonly T _fixture; protected StoreOtherOpsTests(T fixture) { @@ -11,22 +11,22 @@ protected StoreOtherOpsTests(T fixture) { fixture.TypeMapper.RegisterKnownEventTypes(typeof(BookingEvents.BookingImported).Assembly); } - [Fact] - [Trait("Category", "Store")] - public async Task StreamShouldExist() { + [Test] + [Category("Store")] + public async Task StreamShouldExist(CancellationToken cancellationToken) { var evt = _fixture.CreateEvent(); var streamName = _fixture.GetStreamName(); await _fixture.AppendEvent(streamName, evt, ExpectedStreamVersion.NoStream); - var exists = await _fixture.EventStore.StreamExists(streamName, default); - exists.Should().BeTrue(); + var exists = await _fixture.EventStore.StreamExists(streamName, cancellationToken); + await Assert.That(exists).IsTrue(); } - [Fact] - [Trait("Category", "Store")] - public async Task StreamShouldNotExist() { + [Test] + [Category("Store")] + public async Task StreamShouldNotExist(CancellationToken cancellationToken) { var streamName = _fixture.GetStreamName(); - var exists = await _fixture.EventStore.StreamExists(streamName, default); - exists.Should().BeFalse(); + var exists = await _fixture.EventStore.StreamExists(streamName, cancellationToken); + await Assert.That(exists).IsFalse(); } } diff --git a/src/Core/test/Eventuous.Tests.Persistence.Base/Store/Read.cs b/src/Core/test/Eventuous.Tests.Persistence.Base/Store/Read.cs index 06cdbf1c4..45e5ce2d2 100644 --- a/src/Core/test/Eventuous.Tests.Persistence.Base/Store/Read.cs +++ b/src/Core/test/Eventuous.Tests.Persistence.Base/Store/Read.cs @@ -6,7 +6,7 @@ namespace Eventuous.Tests.Persistence.Base.Store; -public abstract class StoreReadTests : IClassFixture where T : StoreFixtureBase { +public abstract class StoreReadTests where T : StoreFixtureBase { readonly T _fixture; protected StoreReadTests(T fixture) { @@ -14,72 +14,71 @@ protected StoreReadTests(T fixture) { _fixture = fixture; } - [Fact] - [Trait("Category", "Store")] - public async Task ShouldReadOne() { + [Test] + [Category("Store")] + public async Task ShouldReadOne(CancellationToken cancellationToken) { var evt = _fixture.CreateEvent(); var streamName = _fixture.GetStreamName(); await _fixture.AppendEvent(streamName, evt, ExpectedStreamVersion.NoStream); - var result = await _fixture.EventStore.ReadEvents(streamName, StreamReadPosition.Start, 100, default); - result.Length.Should().Be(1); - result[0].Payload.Should().BeEquivalentTo(evt); + var result = await _fixture.EventStore.ReadEvents(streamName, StreamReadPosition.Start, 100, cancellationToken); + await Assert.That(result.Length).IsEqualTo(1); + await Assert.That(result[0].Payload).IsEquivalentTo(evt); } - [Fact] - [Trait("Category", "Store")] - public async Task ShouldReadMany() { + [Test] + [Category("Store")] + public async Task ShouldReadMany(CancellationToken cancellationToken) { object[] events = _fixture.CreateEvents(20).ToArray(); var streamName = _fixture.GetStreamName(); await _fixture.AppendEvents(streamName, events, ExpectedStreamVersion.NoStream); - var result = await _fixture.EventStore.ReadEvents(streamName, StreamReadPosition.Start, 100, default); + var result = await _fixture.EventStore.ReadEvents(streamName, StreamReadPosition.Start, 100, cancellationToken); var actual = result.Select(x => x.Payload); - actual.Should().BeEquivalentTo(events); + await Assert.That(actual).IsEquivalentTo(events); } - [Fact] - [Trait("Category", "Store")] - public async Task ShouldReadTail() { + [Test] + [Category("Store")] + public async Task ShouldReadTail(CancellationToken cancellationToken) { object[] events = _fixture.CreateEvents(20).ToArray(); var streamName = _fixture.GetStreamName(); await _fixture.AppendEvents(streamName, events, ExpectedStreamVersion.NoStream); - var result = await _fixture.EventStore.ReadEvents(streamName, new(10), 100, default); + var result = await _fixture.EventStore.ReadEvents(streamName, new(10), 100, cancellationToken); var expected = events.Skip(10); var actual = result.Select(x => x.Payload); - actual.Should().BeEquivalentTo(expected); + await Assert.That(actual).IsEquivalentTo(expected); } - [Fact] - [Trait("Category", "Store")] - public async Task ShouldReadHead() { + [Test] + [Category("Store")] + public async Task ShouldReadHead(CancellationToken cancellationToken) { object[] events = _fixture.CreateEvents(20).ToArray(); var streamName = _fixture.GetStreamName(); await _fixture.AppendEvents(streamName, events, ExpectedStreamVersion.NoStream); - var result = await _fixture.EventStore.ReadEvents(streamName, StreamReadPosition.Start, 10, default); + var result = await _fixture.EventStore.ReadEvents(streamName, StreamReadPosition.Start, 10, cancellationToken); var expected = events.Take(10); - var actual = result.Select(x => x.Payload); - actual.Should().BeEquivalentTo(expected); + + IEnumerable actual = result.Select(x => x.Payload)!; + await Assert.That(actual).IsEquivalentCollectionTo(expected); } - [Fact] - [Trait("Category", "Store")] - public async Task ShouldReadMetadata() { + [Test] + [Category("Store")] + public async Task ShouldReadMetadata(CancellationToken cancellationToken) { var evt = _fixture.CreateEvent(); var streamName = _fixture.GetStreamName(); await _fixture.AppendEvent(streamName, evt, ExpectedStreamVersion.NoStream, new() { { "Key1", "Value1" }, { "Key2", "Value2" } }); - var result = await _fixture.EventStore.ReadEvents(streamName, StreamReadPosition.Start, 100, default); + var result = await _fixture.EventStore.ReadEvents(streamName, StreamReadPosition.Start, 100, cancellationToken); - result.Length.Should().Be(1); - result[0].Payload.Should().BeEquivalentTo(evt); + await Assert.That(result.Length).IsEqualTo(1); + await Assert.That(result[0].Payload).IsEquivalentTo(evt); - result[0] - .Metadata.ToDictionary(m => m.Key, m => ((JsonElement)m.Value!).GetString()) - .Should() - .Contain([new("Key1", "Value1"), new("Key2", "Value2")]); + await Assert.That(result[0].Metadata.ToDictionary(m => m.Key, m => ((JsonElement)m.Value!).GetString())) + .ContainsKey("Key1").And.ContainsKey("Key2"); } } diff --git a/src/Core/test/Eventuous.Tests.Persistence.Base/Store/TieredStoreTests.cs b/src/Core/test/Eventuous.Tests.Persistence.Base/Store/TieredStoreTests.cs index 1f32ef430..0a65b7ed3 100644 --- a/src/Core/test/Eventuous.Tests.Persistence.Base/Store/TieredStoreTests.cs +++ b/src/Core/test/Eventuous.Tests.Persistence.Base/Store/TieredStoreTests.cs @@ -1,3 +1,4 @@ +using AutoFixture; using DotNet.Testcontainers.Containers; using Eventuous.Tests.Persistence.Base.Fixtures; using JetBrains.Annotations; @@ -10,7 +11,7 @@ protected async Task Should_load_hot_and_archive() { var store = _storeFixture.EventStore; var archive = new ArchiveStore(_storeFixture.EventStore); - var testEvents = _fixture.CreateMany(count).ToList(); + var testEvents = _fixture.CreateMany(count).ToArray(); var stream = new StreamName($"Test-{Guid.NewGuid():N}"); await store.Store(stream, ExpectedStreamVersion.NoStream, testEvents); @@ -21,10 +22,10 @@ protected async Task Should_load_hot_and_archive() { var loaded = (await combined.ReadStream(stream, StreamReadPosition.Start)).ToArray(); var actual = loaded.Select(x => (TestEventForTiers)x.Payload!).ToArray(); - actual.Should().BeEquivalentTo(testEvents); + await Assert.That(actual).IsEquivalentCollectionTo(testEvents); - loaded.Take(50).Select(x => x.FromArchive).Should().AllSatisfy(x => x.Should().BeTrue()); - loaded.Skip(50).Select(x => x.FromArchive).Should().AllSatisfy(x => x.Should().BeFalse()); + await Assert.That(loaded.Take(50).Select(x => x.FromArchive)).DoesNotContain(false); + await Assert.That(loaded.Skip(50).Select(x => x.FromArchive)).DoesNotContain(true); } readonly Fixture _fixture = new(); diff --git a/src/Core/test/Eventuous.Tests.Persistence.Base/Traits/CategoryAttribute.cs b/src/Core/test/Eventuous.Tests.Persistence.Base/Traits/CategoryAttribute.cs deleted file mode 100644 index 6678197ac..000000000 --- a/src/Core/test/Eventuous.Tests.Persistence.Base/Traits/CategoryAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JetBrains.Annotations; - -// ReSharper disable once CheckNamespace - -namespace Xunit; - -using Sdk; - -/// -/// Apply this attribute to your test method to specify a category. -/// -[UsedImplicitly] -[TraitDiscoverer("CategoryDiscoverer", "TraitExtensibility")] -[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] -public class CategoryAttribute(string category) : Attribute, ITraitAttribute { - [UsedImplicitly] - public string Name { get; } = category; -} diff --git a/src/Core/test/Eventuous.Tests.Persistence.Base/Traits/CategoryDiscoverer.cs b/src/Core/test/Eventuous.Tests.Persistence.Base/Traits/CategoryDiscoverer.cs deleted file mode 100644 index f696e803f..000000000 --- a/src/Core/test/Eventuous.Tests.Persistence.Base/Traits/CategoryDiscoverer.cs +++ /dev/null @@ -1,24 +0,0 @@ -using JetBrains.Annotations; - -// ReSharper disable once CheckNamespace -namespace Xunit; - -using Sdk; - -/// -/// This class discovers all the tests and test classes that have -/// applied the Category attribute -/// -[UsedImplicitly] -public class CategoryDiscoverer : ITraitDiscoverer { - /// - /// Gets the trait values from the Category attribute. - /// - /// The trait attribute containing the trait values. - /// The trait values. - public IEnumerable> GetTraits(IAttributeInfo traitAttribute) { - var categoryName = traitAttribute.GetNamedArgument("Name"); - - yield return new("Category", categoryName); - } -} diff --git a/src/Core/test/Eventuous.Tests.Persistence/Eventuous.Tests.Persistence.csproj b/src/Core/test/Eventuous.Tests.Persistence/Eventuous.Tests.Persistence.csproj deleted file mode 100644 index 959329ee0..000000000 --- a/src/Core/test/Eventuous.Tests.Persistence/Eventuous.Tests.Persistence.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - true - true - - - - - diff --git a/src/Core/test/Eventuous.Tests.Persistence/TieredStoreTests.cs b/src/Core/test/Eventuous.Tests.Persistence/TieredStoreTests.cs deleted file mode 100644 index e3913a27e..000000000 --- a/src/Core/test/Eventuous.Tests.Persistence/TieredStoreTests.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Eventuous.Testing; - -namespace Eventuous.Tests.Persistence; - -public class TieredStoreTests { - readonly InMemoryEventStore _hotStore = new(); - readonly InMemoryEventStore _coldStore = new(); - - // [Fact] - // public async Task -} diff --git a/src/Core/test/Eventuous.Tests.Subscriptions.Base/Eventuous.Tests.Subscriptions.Base.csproj b/src/Core/test/Eventuous.Tests.Subscriptions.Base/Eventuous.Tests.Subscriptions.Base.csproj index ab4bffe81..8a4a253cd 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions.Base/Eventuous.Tests.Subscriptions.Base.csproj +++ b/src/Core/test/Eventuous.Tests.Subscriptions.Base/Eventuous.Tests.Subscriptions.Base.csproj @@ -2,8 +2,10 @@ true true + false + @@ -12,5 +14,8 @@ + + + diff --git a/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/SubscriptionExtensions.cs b/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/SubscriptionExtensions.cs index 25af43ffb..16edd47ae 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/SubscriptionExtensions.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/SubscriptionExtensions.cs @@ -1,18 +1,19 @@ using Eventuous.Subscriptions; +using Microsoft.Extensions.Logging; namespace Eventuous.Tests.Subscriptions.Base; public static class SubscriptionExtensions { - public static ValueTask SubscribeWithLog(this IMessageSubscription subscription, ILogger log) + public static ValueTask SubscribeWithLog(this IMessageSubscription subscription, ILogger log, CancellationToken cancellationToken = default) => subscription.Subscribe( id => log.LogInformation("{Subscription} subscribed", id), (id, reason, ex) => log.LogWarning(ex, "{Subscription} dropped {Reason}", id, reason), - CancellationToken.None + cancellationToken ); - public static ValueTask UnsubscribeWithLog(this IMessageSubscription subscription, ILogger log) + public static ValueTask UnsubscribeWithLog(this IMessageSubscription subscription, ILogger log, CancellationToken cancellationToken = default) => subscription.Unsubscribe( id => log.LogInformation("{Subscription} unsubscribed", id), - CancellationToken.None + cancellationToken ); } \ No newline at end of file diff --git a/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/SubscriptionFixtureBase.cs b/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/SubscriptionFixtureBase.cs index 5c5534895..96f34f43c 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/SubscriptionFixtureBase.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/SubscriptionFixtureBase.cs @@ -2,31 +2,29 @@ using Eventuous.Subscriptions; using Eventuous.Subscriptions.Checkpoints; using Eventuous.Sut.Domain; +using Eventuous.TestHelpers.TUnit.Logging; using Eventuous.Tests.Persistence.Base.Fixtures; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Eventuous.Tests.Subscriptions.Base; public abstract class SubscriptionFixtureBase - : StoreFixtureBase + : StoreFixtureBase, IStartableFixture where TEventHandler : class, IEventHandler where TContainer : DockerContainer where TCheckpointStore : class, ICheckpointStore where TSubscription : EventSubscription where TSubscriptionOptions : SubscriptionOptions { - readonly ITestOutputHelper _outputHelper; - readonly bool _autoStart; - readonly LogLevel _logLevel; - - protected SubscriptionFixtureBase( - ITestOutputHelper outputHelper, - bool autoStart = true, - LogLevel logLevel = LogLevel.Trace - ) { - _outputHelper = outputHelper; - _autoStart = autoStart; - _logLevel = logLevel; + readonly bool _autoStart; + readonly LogLevel _logLevel; + + protected SubscriptionFixtureBase(bool autoStart = true, LogLevel logLevel = LogLevel.Trace) { + _autoStart = autoStart; + _logLevel = logLevel; TypeMapper.RegisterKnownEventTypes(typeof(BookingEvents.BookingImported).Assembly); } @@ -63,7 +61,7 @@ protected override void SetupServices(IServiceCollection services) { var host = services.First(x => !x.IsKeyedService && x.ImplementationFactory?.GetType() == typeof(Func)); services.Remove(host); - services.AddLogging(b => ConfigureLogging(b.AddXunit(_outputHelper, _logLevel).SetMinimumLevel(_logLevel))); + services.AddLogging(b => ConfigureLogging(b.ForTests(_logLevel))); } protected override void GetDependencies(IServiceProvider provider) { @@ -85,7 +83,7 @@ public override async Task InitializeAsync() { if (_autoStart) await StartSubscription(); } - public override async Task DisposeAsync() { + public override async ValueTask DisposeAsync() { if (_autoStart) await StopSubscription(); await base.DisposeAsync(); } diff --git a/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/TestEventHandler.cs b/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/TestEventHandler.cs index 0f3f0f9b5..8f0e29762 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/TestEventHandler.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions.Base/Fixtures/TestEventHandler.cs @@ -2,7 +2,9 @@ using Eventuous.Subscriptions.Context; using Hypothesist; using Hypothesist.Builders; + // ReSharper disable NotAccessedPositionalProperty.Global +// ReSharper disable MethodHasAsyncOverload namespace Eventuous.Tests.Subscriptions.Base; @@ -12,7 +14,9 @@ public record TestEvent(string Data, int Number) { public const string TypeName = "test-event"; } -public class TestEventHandler(TestEventHandlerOptions? options = null) : BaseEventHandler { +public class TestEventHandler(TestEventHandlerOptions? options) : BaseEventHandler { + public TestEventHandler() : this(null) { } + readonly TimeSpan _delay = options?.Delay ?? TimeSpan.Zero; public int Count { get; private set; } @@ -25,7 +29,7 @@ public Hypothesis AssertCollection(TimeSpan deadline, List colle => Hypothesis.On(_observer).Timebox(deadline).Exactly(collection.Count).Match(collection.Contains); public override async ValueTask HandleEvent(IMessageConsumeContext context) { - options?.Output?.WriteLine(context.Message!.ToString()); + TestContext.Current?.OutputWriter.WriteLine(context.Message!.ToString() ?? "Unknown"); await Task.Delay(_delay); await _observer.Add(context.Message!, context.CancellationToken); Count++; @@ -36,4 +40,4 @@ public override async ValueTask HandleEvent(IMessageConsume public void Reset() => Count = 0; } -public record TestEventHandlerOptions(TimeSpan? Delay = null, ITestOutputHelper? Output = null); +public record TestEventHandlerOptions(TimeSpan? Delay = null); diff --git a/src/Core/test/Eventuous.Tests.Subscriptions.Base/SubscribeToAll.cs b/src/Core/test/Eventuous.Tests.Subscriptions.Base/SubscribeToAll.cs index cf4fbb30b..5dee0cea3 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions.Base/SubscribeToAll.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions.Base/SubscribeToAll.cs @@ -9,26 +9,25 @@ namespace Eventuous.Tests.Subscriptions.Base; public abstract class SubscribeToAllBase( - ITestOutputHelper outputHelper, SubscriptionFixtureBase fixture - ) : IAsyncLifetime + ) : SubscriptionTestBase(fixture) where TContainer : DockerContainer where TSubscription : EventSubscription where TSubscriptionOptions : SubscriptionOptions where TCheckpointStore : class, ICheckpointStore { - protected async Task ShouldConsumeProducedEvents() { + protected async Task ShouldConsumeProducedEvents(CancellationToken cancellationToken) { const int count = 10; var commands = await GenerateAndHandleCommands(count); var testEvents = commands.Select(ToEvent).ToList(); await fixture.StartSubscription(); - await fixture.Handler.AssertCollection(2.Seconds(), [..testEvents]).Validate(); + await fixture.Handler.AssertCollection(TimeSpan.FromSeconds(2), [..testEvents]).Validate(cancellationToken); await fixture.StopSubscription(); - fixture.Handler.Count.Should().Be(10); + await Assert.That(fixture.Handler.Count).IsEqualTo(10); } - protected async Task ShouldConsumeProducedEventsWhenRestarting() { + protected async Task ShouldConsumeProducedEventsWhenRestarting(CancellationToken cancellationToken) { await TestConsumptionOfProducedEvents(); fixture.Handler.Reset(); @@ -39,32 +38,33 @@ protected async Task ShouldConsumeProducedEventsWhenRestarting() { return; async Task TestConsumptionOfProducedEvents() { - const int count = 10; - var commands = await GenerateAndHandleCommands(count); - var testEvents = commands.Select(ToEvent).ToList(); + const int count = 10; + + var commands = await GenerateAndHandleCommands(count); + var testEvents = commands.Select(ToEvent).ToList(); await fixture.StartSubscription(); - await fixture.Handler.AssertCollection(2.Seconds(), [..testEvents]).Validate(); + await fixture.Handler.AssertCollection(TimeSpan.FromSeconds(2), [..testEvents]).Validate(cancellationToken); await fixture.StopSubscription(); - fixture.Handler.Count.Should().Be(10); + await Assert.That(fixture.Handler.Count).IsEqualTo(10); } } - protected async Task ShouldUseExistingCheckpoint() { + protected async Task ShouldUseExistingCheckpoint(CancellationToken cancellationToken) { const int count = 10; await GenerateAndHandleCommands(count); - await fixture.CheckpointStore.GetLastCheckpoint(fixture.SubscriptionId, default); + await fixture.CheckpointStore.GetLastCheckpoint(fixture.SubscriptionId, cancellationToken); var last = await fixture.GetLastPosition(); - await fixture.CheckpointStore.StoreCheckpoint(new(fixture.SubscriptionId, last), true, default); - - var l = await fixture.CheckpointStore.GetLastCheckpoint(fixture.SubscriptionId, default); - outputHelper.WriteLine("Last checkpoint: {0}", l.Position); + await fixture.CheckpointStore.StoreCheckpoint(new(fixture.SubscriptionId, last), true, cancellationToken); + + var l = await fixture.CheckpointStore.GetLastCheckpoint(fixture.SubscriptionId, cancellationToken); + TestContext.Current?.OutputWriter.WriteLine("Last checkpoint: {0}", l.Position!); await fixture.StartSubscription(); await Task.Delay(TimeSpan.FromSeconds(1)); await fixture.StopSubscription(); - fixture.Handler.Count.Should().Be(0); + await Assert.That(fixture.Handler.Count).IsEqualTo(0); } static BookingImported ToEvent(ImportBooking cmd) => new(cmd.RoomId, cmd.Price, cmd.CheckIn, cmd.CheckOut); @@ -84,8 +84,4 @@ async Task> GenerateAndHandleCommands(int count) { return commands; } - - public Task InitializeAsync() => fixture.InitializeAsync(); - - public Task DisposeAsync() => fixture.DisposeAsync(); } diff --git a/src/Core/test/Eventuous.Tests.Subscriptions.Base/SubscribeToStream.cs b/src/Core/test/Eventuous.Tests.Subscriptions.Base/SubscribeToStream.cs index 7bbea63fa..0743ecc17 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions.Base/SubscribeToStream.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions.Base/SubscribeToStream.cs @@ -6,81 +6,83 @@ using static Eventuous.Sut.App.Commands; using static Eventuous.Sut.Domain.BookingEvents; +// ReSharper disable MethodHasAsyncOverload + namespace Eventuous.Tests.Subscriptions.Base; public abstract class SubscribeToStreamBase( - ITestOutputHelper outputHelper, StreamName streamName, SubscriptionFixtureBase fixture - ) : IAsyncLifetime + ) : SubscriptionTestBase(fixture) where TContainer : DockerContainer where TSub : EventSubscription where TSubOptions : SubscriptionOptions where TCheckpointStore : class, ICheckpointStore { - protected async Task ShouldConsumeProducedEvents() { - const int count = 10; + protected async Task ShouldConsumeProducedEvents(CancellationToken cancellationToken) { + const int count = 10; + const ulong expected = count - 1; var testEvents = await GenerateAndProduceEvents(count); await fixture.StartSubscription(); - await fixture.Handler.AssertCollection(2.Seconds(), [..testEvents]).Validate(); + await fixture.Handler.AssertCollection(TimeSpan.FromSeconds(2), [..testEvents]).Validate(cancellationToken); await fixture.StopSubscription(); - fixture.Handler.Count.Should().Be(10); + await Assert.That(fixture.Handler.Count).IsEqualTo(10); - var checkpoint = await fixture.CheckpointStore.GetLastCheckpoint(fixture.SubscriptionId, default); - checkpoint.Position.Should().Be(count - 1); + var checkpoint = await fixture.CheckpointStore.GetLastCheckpoint(fixture.SubscriptionId, cancellationToken); + await Assert.That(checkpoint.Position).IsEqualTo(expected); } - protected async Task ShouldConsumeProducedEventsWhenRestarting() { - outputHelper.WriteLine("Phase one"); + protected async Task ShouldConsumeProducedEventsWhenRestarting(CancellationToken cancellationToken) { + TestContext.Current?.OutputWriter.WriteLine("Phase one"); await TestConsumptionOfProducedEvents(); - outputHelper.WriteLine("Resetting handler"); + TestContext.Current?.OutputWriter.WriteLine("Resetting handler"); fixture.Handler.Reset(); - outputHelper.WriteLine("Phase two"); + TestContext.Current?.OutputWriter.WriteLine("Phase two"); await TestConsumptionOfProducedEvents(); - var checkpoint = await fixture.CheckpointStore.GetLastCheckpoint(fixture.SubscriptionId, default); - checkpoint.Position.Should().Be(19); + var checkpoint = await fixture.CheckpointStore.GetLastCheckpoint(fixture.SubscriptionId, cancellationToken); + await Assert.That(checkpoint.Position).IsEqualTo(19UL); return; async Task TestConsumptionOfProducedEvents() { const int count = 10; - outputHelper.WriteLine("Generating and producing events"); + TestContext.Current?.OutputWriter.WriteLine("Generating and producing events"); var testEvents = await GenerateAndProduceEvents(count); - outputHelper.WriteLine("Starting subscription"); + TestContext.Current?.OutputWriter.WriteLine("Starting subscription"); await fixture.StartSubscription(); - await fixture.Handler.AssertCollection(2.Seconds(), [..testEvents]).Validate(); - outputHelper.WriteLine("Stopping subscription"); + await fixture.Handler.AssertCollection(TimeSpan.FromSeconds(2), [..testEvents]).Validate(); + TestContext.Current?.OutputWriter.WriteLine("Stopping subscription"); await fixture.StopSubscription(); - fixture.Handler.Count.Should().Be(10); + await Assert.That(fixture.Handler.Count).IsEqualTo(10); } } - public async Task ShouldUseExistingCheckpoint() { + public async Task ShouldUseExistingCheckpoint(CancellationToken cancellationToken) { const int count = 10; await GenerateAndProduceEvents(count); - await fixture.CheckpointStore.GetLastCheckpoint(fixture.SubscriptionId, default); + await fixture.CheckpointStore.GetLastCheckpoint(fixture.SubscriptionId, cancellationToken); Logger.ConfigureIfNull(fixture.SubscriptionId, fixture.LoggerFactory); - await fixture.CheckpointStore.StoreCheckpoint(new(fixture.SubscriptionId, 9), true, default); + await fixture.CheckpointStore.StoreCheckpoint(new(fixture.SubscriptionId, 9), true, cancellationToken); await fixture.StartSubscription(); - await Task.Delay(TimeSpan.FromSeconds(1)); + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); await fixture.StopSubscription(); - fixture.Handler.Count.Should().Be(0); + await Assert.That(fixture.Handler.Count).IsEqualTo(0); } static BookingImported ToEvent(ImportBooking cmd) => new(cmd.RoomId, cmd.Price, cmd.CheckIn, cmd.CheckOut); async Task> GenerateAndProduceEvents(int count) { - outputHelper.WriteLine($"Producing events to {streamName}"); + await TestContext.Current!.OutputWriter.WriteLineAsync($"Producing events to {streamName}")!; var commands = Enumerable .Range(0, count) @@ -94,7 +96,7 @@ async Task> GenerateAndProduceEvents(int count) { return events; } - public Task InitializeAsync() => fixture.InitializeAsync(); + protected async Task InitializeAsync() => await fixture.InitializeAsync(); - public Task DisposeAsync() => fixture.DisposeAsync(); + protected async Task DisposeAsync() => await fixture.DisposeAsync(); } diff --git a/src/Core/test/Eventuous.Tests.Subscriptions.Base/SubscriptionTestBase.cs b/src/Core/test/Eventuous.Tests.Subscriptions.Base/SubscriptionTestBase.cs new file mode 100644 index 000000000..2d0fe3894 --- /dev/null +++ b/src/Core/test/Eventuous.Tests.Subscriptions.Base/SubscriptionTestBase.cs @@ -0,0 +1,15 @@ +using Eventuous.Tests.Persistence.Base.Fixtures; + +namespace Eventuous.Tests.Subscriptions.Base; + +public abstract class SubscriptionTestBase(IStartableFixture fixture) { + [Before(Test)] + public async Task Startup() { + await fixture.InitializeAsync(); + } + + [After(Test)] + public async Task Shutdown() { + await fixture.DisposeAsync(); + } +} diff --git a/src/Core/test/Eventuous.Tests.Subscriptions/AutofixtureExtensions.cs b/src/Core/test/Eventuous.Tests.Subscriptions/AutofixtureExtensions.cs index da366b83e..90ddd2e06 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions/AutofixtureExtensions.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions/AutofixtureExtensions.cs @@ -1,10 +1,11 @@ using Eventuous.Subscriptions.Context; +using Eventuous.TestHelpers.TUnit.Logging; namespace Eventuous.Tests.Subscriptions; public static class AutoFixtureExtensions { - public static MessageConsumeContext CreateContext(this Fixture auto, ITestOutputHelper output) { - var factory = new LoggerFactory().AddXunit(output, LogLevel.Trace); + public static MessageConsumeContext CreateContext(this Fixture auto) { + var factory = new LoggerFactory().AddTUnit(); return auto.Build().With(x => x.LogContext, () => new("test", factory)).Create(); } } diff --git a/src/Core/test/Eventuous.Tests.Subscriptions/ConsumePipeTests.cs b/src/Core/test/Eventuous.Tests.Subscriptions/ConsumePipeTests.cs index eae919e30..b7f4051b5 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions/ConsumePipeTests.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions/ConsumePipeTests.cs @@ -4,14 +4,14 @@ namespace Eventuous.Tests.Subscriptions; -public class ConsumePipeTests(ITestOutputHelper outputHelper) { +public class ConsumePipeTests() { static readonly Fixture Auto = new(); - [Fact] + [Test] public async Task ShouldCallHandlers() { var handler = new TestHandler(); var pipe = new ConsumePipe().AddDefaultConsumer(handler); - var ctx = Auto.CreateContext(outputHelper); + var ctx = Auto.CreateContext(); await pipe.Send(ctx); @@ -20,7 +20,7 @@ public async Task ShouldCallHandlers() { const string Key = "test-baggage"; - [Fact] + [Test] public async Task ShouldAddContextBaggage() { var handler = new TestHandler(); var pipe = new ConsumePipe().AddDefaultConsumer(handler); @@ -28,7 +28,7 @@ public async Task ShouldAddContextBaggage() { pipe.AddFilterFirst(new TestFilter(Key, baggage)); - var ctx = Auto.CreateContext(outputHelper); + var ctx = Auto.CreateContext(); await pipe.Send(ctx); diff --git a/src/Core/test/Eventuous.Tests.Subscriptions/DefaultConsumerTests.cs b/src/Core/test/Eventuous.Tests.Subscriptions/DefaultConsumerTests.cs index 4e29823ae..e6c135e47 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions/DefaultConsumerTests.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions/DefaultConsumerTests.cs @@ -1,20 +1,20 @@ using Eventuous.Subscriptions; using Eventuous.Subscriptions.Consumers; using Eventuous.Subscriptions.Context; -using Eventuous.TestHelpers; +using Eventuous.TestHelpers.TUnit; namespace Eventuous.Tests.Subscriptions; -public class DefaultConsumerTests(ITestOutputHelper output) : IDisposable { - readonly TestEventListener _listener = new(output); +public class DefaultConsumerTests() : IDisposable { + readonly TestEventListener _listener = new(); static readonly Fixture Auto = new(); - [Fact] + [Test] public async Task ShouldFailWhenHandlerNacks() { var handler = new FailingHandler(); var consumer = new DefaultConsumer([handler]); - var ctx = Auto.CreateContext(output); + var ctx = Auto.CreateContext(); await consumer.Consume(ctx); diff --git a/src/Core/test/Eventuous.Tests.Subscriptions/Eventuous.Tests.Subscriptions.csproj b/src/Core/test/Eventuous.Tests.Subscriptions/Eventuous.Tests.Subscriptions.csproj index 8ef6c259e..f6b9b4312 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions/Eventuous.Tests.Subscriptions.csproj +++ b/src/Core/test/Eventuous.Tests.Subscriptions/Eventuous.Tests.Subscriptions.csproj @@ -2,10 +2,13 @@ true true + Exe - - - + + + + + diff --git a/src/Core/test/Eventuous.Tests.Subscriptions/HandlingStatusTests.cs b/src/Core/test/Eventuous.Tests.Subscriptions/HandlingStatusTests.cs index b58d7757e..005ae0e3b 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions/HandlingStatusTests.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions/HandlingStatusTests.cs @@ -3,43 +3,43 @@ namespace Eventuous.Tests.Subscriptions; -public class HandlingStatusTests(ITestOutputHelper output) { +public class HandlingStatusTests() { static Fixture Auto { get; } = new(); - [Fact] + [Test] public void AckAndNackShouldNack() { const EventHandlingStatus actual = EventHandlingStatus.Success | EventHandlingStatus.Failure; (actual & EventHandlingStatus.Handled).Should().Be(EventHandlingStatus.Failure); } - [Fact] + [Test] public void AckAndIgnoreShouldAck() { const EventHandlingStatus actual = EventHandlingStatus.Success | EventHandlingStatus.Ignored; (actual & EventHandlingStatus.Handled).Should().Be(EventHandlingStatus.Success); } - [Fact] + [Test] public void NackAndIgnoreShouldNack() { const EventHandlingStatus actual = EventHandlingStatus.Failure | EventHandlingStatus.Ignored; (actual & EventHandlingStatus.Handled).Should().Be(EventHandlingStatus.Failure); } - [Fact] + [Test] public void PendingShouldBeHandled() { const EventHandlingStatus actual = EventHandlingStatus.Pending; (actual & EventHandlingStatus.Handled).Should().NotBe(EventHandlingStatus.Failure); (actual & EventHandlingStatus.Handled).Should().NotBe(EventHandlingStatus.Ignored); } - [Fact] + [Test] public void IgnoredShouldBeIgnored() { const EventHandlingStatus actual = EventHandlingStatus.Ignored; (actual & EventHandlingStatus.Handled).Should().Be(0); } - [Fact] + [Test] public void NackAndIgnoreShouldFail() { - var context = Auto.CreateContext(output); + var context = Auto.CreateContext(); context.Nack(new Exception()); context.Ignore("test"); context.HasFailed().Should().BeTrue(); @@ -47,9 +47,9 @@ public void NackAndIgnoreShouldFail() { context.HandlingResults.IsPending().Should().BeFalse(); } - [Fact] + [Test] public void NackAckAndIgnoreShouldFail() { - var context = Auto.CreateContext(output); + var context = Auto.CreateContext(); context.Nack(new Exception()); context.Ack(); context.Ignore(); @@ -58,9 +58,9 @@ public void NackAckAndIgnoreShouldFail() { context.HandlingResults.IsPending().Should().BeFalse(); } - [Fact] + [Test] public void AckAndIgnoreShouldSucceed() { - var context = Auto.CreateContext(output); + var context = Auto.CreateContext(); context.Ack(); context.Ignore(); context.HasFailed().Should().BeFalse(); @@ -68,18 +68,18 @@ public void AckAndIgnoreShouldSucceed() { context.HandlingResults.IsPending().Should().BeFalse(); } - [Fact] + [Test] public void IgnoreAndIgnoreShouldIgnore() { - var context = Auto.CreateContext(output); + var context = Auto.CreateContext(); context.Ignore(); context.Ignore(); context.WasIgnored().Should().BeTrue(); context.HandlingResults.IsPending().Should().BeFalse(); } - [Fact] + [Test] public void PendingShouldBePending() { - var context = Auto.CreateContext(output); + var context = Auto.CreateContext(); context.WasIgnored().Should().BeFalse(); context.HasFailed().Should().BeFalse(); context.HandlingResults.IsPending().Should().BeTrue(); diff --git a/src/Core/test/Eventuous.Tests.Subscriptions/RegistrationTests.cs b/src/Core/test/Eventuous.Tests.Subscriptions/RegistrationTests.cs index 58eb910d9..3fc505f75 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions/RegistrationTests.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions/RegistrationTests.cs @@ -3,25 +3,24 @@ using Eventuous.Subscriptions.Context; using Eventuous.Subscriptions.Diagnostics; using Eventuous.Subscriptions.Filters; -using Eventuous.Subscriptions.Logging; -using Eventuous.TestHelpers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging.Abstractions; +using LoggingExtensions = Eventuous.TestHelpers.TUnit.Logging.LoggingExtensions; // ReSharper disable ClassNeverInstantiated.Local namespace Eventuous.Tests.Subscriptions; -public class RegistrationTests(ITestOutputHelper outputHelper) { +public class RegistrationTests() { readonly TestServer _server = new(BuildHost()); readonly Fixture _auto = new(); - readonly ILoggerFactory _logger = Logging.GetLoggerFactory(outputHelper); + readonly ILoggerFactory _logger = LoggingExtensions.GetLoggerFactory(); - [Fact] + [Test] public void ShouldBeSingletons() { var subs1 = _server.Services.GetServices().ToArray(); var subs2 = _server.Services.GetServices().ToArray(); @@ -29,22 +28,22 @@ public void ShouldBeSingletons() { subs1[1].Should().BeSameAs(subs2[1]); } - [Fact] + [Test] public void ShouldRegisterBothSubs() { var subs = _server.Services.GetServices().ToArray(); subs.Length.Should().Be(2); } - [Fact] + [Test] public void SubsShouldHaveProperIds() { var subs = _server.Services.GetServices().ToArray(); subs[0].Options.SubscriptionId.Should().Be("sub1"); subs[1].Options.SubscriptionId.Should().Be("sub2"); } - [Theory] - [InlineData(0, typeof(Handler1))] - [InlineData(1, typeof(Handler2))] + [Test] + [Arguments(0, typeof(Handler1))] + [Arguments(1, typeof(Handler2))] public async Task SubsShouldHaveHandlers(int position, Type handlerType) { var subs = _server.Services.GetServices().ToArray(); var logger = _server.Services.GetRequiredService(); @@ -64,7 +63,7 @@ public async Task SubsShouldHaveHandlers(int position, Type handlerType) { new Metadata(), current.SubscriptionId, default - ) { LogContext = new LogContext(current.SubscriptionId, _logger) }; + ) { LogContext = new(current.SubscriptionId, _logger) }; await current.Pipe.Send(ctx); var handled = logger.Records.Where(x => x.Context.SubscriptionId == current.SubscriptionId).ToArray(); @@ -74,7 +73,7 @@ public async Task SubsShouldHaveHandlers(int position, Type handlerType) { handled[0].Context.MessageType.Should().Be(ctx.MessageType); } - [Fact] + [Test] public void ShouldRegisterBothAsHealthReporters() { var services = _server.Services.GetServices().ToArray(); var health = _server.Services.GetServices().ToArray(); @@ -84,8 +83,8 @@ public void ShouldRegisterBothAsHealthReporters() { services.Single().Should().BeSameAs(health.Single()); } - [Fact] - public async Task BothShouldBeRunningAndReportHealthy() { + [Test] + public async Task BothShouldBeRunningAndReportHealthy(CancellationToken cancellationToken) { var subs = _server.Services.GetServices().ToArray(); var health = _server.Services.GetRequiredService() as SubscriptionHealthCheck; @@ -93,13 +92,13 @@ public async Task BothShouldBeRunningAndReportHealthy() { subs.Should().AllSatisfy(x => x.IsRunning.Should().BeTrue()); health.Should().NotBeNull(); - var check = await health!.CheckHealthAsync(new HealthCheckContext()); + var check = await health!.CheckHealthAsync(new(), cancellationToken); check.Data["sub1"].Should().Be("Healthy"); check.Data["sub2"].Should().Be("Healthy"); check.Status.Should().Be(HealthStatus.Healthy); } - [Fact] + [Test] public void ShouldRegisterTwoMeasures() { var subs = _server.Services.GetServices().ToArray(); subs.Should().NotBeEmpty(); @@ -138,24 +137,23 @@ class TestSub(TestOptions options, ConsumePipe consumePipe) protected override ValueTask Unsubscribe(CancellationToken cancellationToken) => default; - public GetSubscriptionEndOfStream GetMeasure() - => _ => new(new EndOfStream(SubscriptionId, 0, DateTime.UtcNow)); + public GetSubscriptionEndOfStream GetMeasure() => _ => new(new EndOfStream(SubscriptionId, 0, DateTime.UtcNow)); } - abstract class BaseTestHandler(TestHandlerLogger logger) : BaseEventHandler { + public abstract class BaseTestHandler(TestHandlerLogger logger) : BaseEventHandler { public override ValueTask HandleEvent(IMessageConsumeContext ctx) => logger.EventReceived(GetType(), ctx); } - class Handler1(TestHandlerLogger logger) : BaseTestHandler(logger); + public class Handler1(TestHandlerLogger logger) : BaseTestHandler(logger); - class Handler2(TestHandlerLogger logger) : BaseTestHandler(logger); + public class Handler2(TestHandlerLogger logger) : BaseTestHandler(logger); record TestEvent; } -class TestHandlerLogger { +public class TestHandlerLogger { public ValueTask EventReceived(Type handlerType, IMessageConsumeContext ctx) { - Records.Add(new TestHandlerLogRecord(handlerType, ctx)); + Records.Add(new(handlerType, ctx)); return ValueTask.FromResult(EventHandlingStatus.Success); } @@ -163,4 +161,4 @@ public ValueTask EventReceived(Type handlerType, IMessageCo public List Records { get; } = []; } -record TestHandlerLogRecord(Type HandlerType, IMessageConsumeContext Context); +public record TestHandlerLogRecord(Type HandlerType, IMessageConsumeContext Context); diff --git a/src/Core/test/Eventuous.Tests.Subscriptions/SequenceTests.cs b/src/Core/test/Eventuous.Tests.Subscriptions/SequenceTests.cs index 129d889ad..ef029b647 100644 --- a/src/Core/test/Eventuous.Tests.Subscriptions/SequenceTests.cs +++ b/src/Core/test/Eventuous.Tests.Subscriptions/SequenceTests.cs @@ -1,42 +1,42 @@ using Eventuous.Subscriptions.Checkpoints; +using Eventuous.TestHelpers.TUnit.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Xunit.Extensions.Logging; namespace Eventuous.Tests.Subscriptions; public class SequenceTests { - public SequenceTests(ITestOutputHelper output) { + public SequenceTests() { var factory = new LoggerFactory(); - factory.AddProvider(new XunitLoggerProvider(output, (_, _) => true)); + factory.AddProvider(new TUnitLoggerProvider()); var services = new ServiceCollection(); services.AddSingleton(factory); var provider = services.BuildServiceProvider(); provider.AddEventuousLogs(); } - [Theory] - [MemberData(nameof(TestData))] + [Test] + [MethodDataSource(nameof(TestData))] public void ShouldReturnFirstBefore(CommitPositionSequence sequence, CommitPosition expected) { var first = sequence.FirstBeforeGap(); first.Should().Be(expected); } - [Fact] + [Test] public void ShouldWorkForOne() { var timestamp = DateTime.Now; var sequence = new CommitPositionSequence { new(0, 1, timestamp) }; sequence.FirstBeforeGap().Should().Be(new CommitPosition(0, 1, timestamp)); } - [Fact] + [Test] public void ShouldWorkForRandomGap() { var random = new Random(); var sequence = new CommitPositionSequence(); var start = (ulong)random.Next(1); for (var i = start; i < start + 100; i++) { - sequence.Add(new CommitPosition(i, i, DateTime.Now)); + sequence.Add(new(i, i, DateTime.Now)); } var gapPlace = random.Next(1, sequence.Count - 1); @@ -47,7 +47,7 @@ public void ShouldWorkForRandomGap() { first.Should().Be(sequence.ElementAt(gapPlace - 1)); } - [Fact] + [Test] public void ShouldWorkForNormalCase() { var sequence = new CommitPositionSequence(); var timestamp = DateTime.Now; @@ -60,21 +60,10 @@ public void ShouldWorkForNormalCase() { first.Should().Be(new CommitPosition(9, 9, timestamp)); } - public static IEnumerable TestData { - get { - var timestamp = DateTime.Now; - - object[] sequence1 = [ - new CommitPositionSequence { new(0, 1, timestamp), new(0, 2, timestamp), new(0, 4, timestamp), new(0, 6, timestamp) }, - new CommitPosition(0, 2, timestamp) - ]; - - object[] sequence2 = [ - new CommitPositionSequence { new(0, 1, timestamp), new(0, 2, timestamp), new(0, 8, timestamp), new(0, 6, timestamp) }, - new CommitPosition(0, 2, timestamp) - ]; + public static IEnumerable<(CommitPositionSequence, CommitPosition)> TestData() { + var timestamp = DateTime.Now; - return [sequence1, sequence2]; - } + yield return ([new(0, 1, timestamp), new(0, 2, timestamp), new(0, 4, timestamp), new(0, 6, timestamp)], new(0, 2, timestamp)); + yield return ([new(0, 1, timestamp), new(0, 2, timestamp), new(0, 8, timestamp), new(0, 6, timestamp)], new(0, 2, timestamp)); } } diff --git a/src/Core/test/Eventuous.Tests/AggregateWithId/OperateOnAggregateWithId.cs b/src/Core/test/Eventuous.Tests/AggregateWithId/OperateOnAggregateWithId.cs index 59d0dd3dd..c44bf364e 100644 --- a/src/Core/test/Eventuous.Tests/AggregateWithId/OperateOnAggregateWithId.cs +++ b/src/Core/test/Eventuous.Tests/AggregateWithId/OperateOnAggregateWithId.cs @@ -9,10 +9,10 @@ public class OperateOnAggregateWithId : AggregateWithIdSpec Emitted(new TestEvent()); - [Fact] + [Test] public void should_set_id() => Then().State.Id.Value.Should().Be(IdValue); } diff --git a/src/Core/test/Eventuous.Tests/Aggregates/OperateOnExistingSpec.cs b/src/Core/test/Eventuous.Tests/Aggregates/OperateOnExistingSpec.cs index 8dad42a06..b0c7b6694 100644 --- a/src/Core/test/Eventuous.Tests/Aggregates/OperateOnExistingSpec.cs +++ b/src/Core/test/Eventuous.Tests/Aggregates/OperateOnExistingSpec.cs @@ -10,21 +10,21 @@ protected override object[] GivenEvents() => [ protected override void When(Booking booking) => booking.RecordPayment("payment1", new(50), DateTimeOffset.Now); - [Fact] + [Test] public void should_produce_payment_registered() => Emitted(new BookingEvents.BookingPaymentRegistered("payment1", 50)); - [Fact] + [Test] public void should_produce_outstanding_changed() => Emitted(new BookingEvents.BookingOutstandingAmountChanged(50)); - [Fact] + [Test] public void should_not_be_fully_paid() => Then().State.IsFullyPaid().Should().BeFalse(); - [Fact] + [Test] public void should_record_payment() => Then().HasPaymentRecord("payment1").Should().BeTrue(); - [Fact] + [Test] public void should_not_be_overpaid() => Then().State.IsOverpaid().Should().BeFalse(); - [Fact] + [Test] public void should_produce_two_events() => Then().Changes.Should().HaveCount(2); } diff --git a/src/Core/test/Eventuous.Tests/Aggregates/TwoAggregateOpsSpec.cs b/src/Core/test/Eventuous.Tests/Aggregates/TwoAggregateOpsSpec.cs index d4b7008a1..3e6ad68ad 100644 --- a/src/Core/test/Eventuous.Tests/Aggregates/TwoAggregateOpsSpec.cs +++ b/src/Core/test/Eventuous.Tests/Aggregates/TwoAggregateOpsSpec.cs @@ -20,22 +20,22 @@ protected override void When(Booking booking) { booking.RecordPayment(_testData.PaymentId, amount, _testData.PaidAt); } - [Fact] + [Test] public void should_produce_fully_paid_event() => Emitted(new BookingFullyPaid(_testData.PaidAt)); - [Fact] + [Test] public void should_produce_payment_registered() => Emitted(new BookingPaymentRegistered(_testData.PaymentId, _testData.Amount)); - [Fact] + [Test] public void should_produce_outstanding_changed() => Emitted(new BookingOutstandingAmountChanged(0)); - [Fact] + [Test] public void should_make_booking_fully_paid() => Then().State.IsFullyPaid().Should().BeTrue(); - [Fact] + [Test] public void should_record_payment() => Then().HasPaymentRecord(_testData.PaymentId).Should().BeTrue(); - [Fact] + [Test] public void should_not_be_overpaid() => Then().State.IsOverpaid().Should().BeFalse(); readonly TestData _testData; diff --git a/src/Core/test/Eventuous.Tests/Eventuous.Tests.csproj b/src/Core/test/Eventuous.Tests/Eventuous.Tests.csproj index 912bd2e69..13938e703 100644 --- a/src/Core/test/Eventuous.Tests/Eventuous.Tests.csproj +++ b/src/Core/test/Eventuous.Tests/Eventuous.Tests.csproj @@ -2,13 +2,22 @@ true true + Exe + true + true + true - - + + + + - - + + + + + diff --git a/src/Core/test/Eventuous.Tests/ForgotToSetId.cs b/src/Core/test/Eventuous.Tests/ForgotToSetId.cs index 98500d1e5..4f43d7742 100644 --- a/src/Core/test/Eventuous.Tests/ForgotToSetId.cs +++ b/src/Core/test/Eventuous.Tests/ForgotToSetId.cs @@ -5,10 +5,10 @@ namespace Eventuous.Tests; public class ForgotToSetId : NaiveFixture { public ForgotToSetId() => Service = new(this.EventStore); - [Fact] - public async Task ShouldFailWithNoId() { + [Test] + public async Task ShouldFailWithNoId(CancellationToken cancellationToken) { var cmd = new DoIt(Auto.Create()); - var result = await Service.Handle(cmd, default); + var result = await Service.Handle(cmd, cancellationToken); result.Success.Should().BeTrue(); } diff --git a/src/Core/test/Eventuous.Tests/StoringEvents.cs b/src/Core/test/Eventuous.Tests/StoringEvents.cs index 7c74c9bcb..f574f0ba9 100644 --- a/src/Core/test/Eventuous.Tests/StoringEvents.cs +++ b/src/Core/test/Eventuous.Tests/StoringEvents.cs @@ -15,8 +15,8 @@ public StoringEvents() { BookingService Service { get; } - [Fact] - public async Task StoreInitial() { + [Test] + public async Task StoreInitial(CancellationToken cancellationToken) { var cmd = new Commands.BookRoom( Auto.Create(), Auto.Create(), @@ -27,7 +27,7 @@ public async Task StoreInitial() { Change[] expected = [new(new RoomBooked(cmd.RoomId, cmd.CheckIn, cmd.CheckOut, cmd.Price), TypeNames.RoomBooked)]; - var result = await Service.Handle(cmd, default); + var result = await Service.Handle(cmd, cancellationToken); result.TryGet(out var ok).Should().BeTrue(); ok!.Changes.Should().BeEquivalentTo(expected); diff --git a/src/Core/test/Eventuous.Tests/StoringEventsWithCustomStream.cs b/src/Core/test/Eventuous.Tests/StoringEventsWithCustomStream.cs index 269207e5d..53b5fcba8 100644 --- a/src/Core/test/Eventuous.Tests/StoringEventsWithCustomStream.cs +++ b/src/Core/test/Eventuous.Tests/StoringEventsWithCustomStream.cs @@ -16,13 +16,13 @@ public StoringEventsWithCustomStream() { BookingService Service { get; } - [Fact] - public async Task TestOnNew() { + [Test] + public async Task TestOnNew(CancellationToken cancellationToken) { var cmd = CreateBookRoomCommand(); Change[] expected = [new(new RoomBooked(cmd.RoomId, cmd.CheckIn, cmd.CheckOut, cmd.Price), TypeNames.RoomBooked)]; - var result = await Service.Handle(cmd, default); + var result = await Service.Handle(cmd, cancellationToken); result.TryGet(out var ok).Should().BeTrue(); ok!.Changes.Should().BeEquivalentTo(expected); @@ -32,11 +32,11 @@ public async Task TestOnNew() { evt[0].Payload.Should().BeEquivalentTo(ok.Changes.First().Event); } - [Fact] - public async Task TestOnExisting() { + [Test] + public async Task TestOnExisting(CancellationToken cancellationToken) { var cmd = CreateBookRoomCommand(); - await Service.Handle(cmd, default); + await Service.Handle(cmd, cancellationToken); var secondCmd = new Commands.RecordPayment(new(cmd.BookingId), Auto.Create(), new(cmd.Price), DateTimeOffset.Now); @@ -46,7 +46,7 @@ public async Task TestOnExisting() { new(new BookingFullyPaid(secondCmd.PaidAt), TypeNames.BookingFullyPaid) }; - var result = await Service.Handle(secondCmd, default); + var result = await Service.Handle(secondCmd, cancellationToken); result.TryGet(out var ok).Should().BeTrue(); ok!.Changes.Should().BeEquivalentTo(expected); diff --git a/src/Core/test/Eventuous.Tests/StreamNameMapTests.cs b/src/Core/test/Eventuous.Tests/StreamNameMapTests.cs index f096554ea..34598ea81 100644 --- a/src/Core/test/Eventuous.Tests/StreamNameMapTests.cs +++ b/src/Core/test/Eventuous.Tests/StreamNameMapTests.cs @@ -7,19 +7,19 @@ namespace Eventuous.Tests; public class StreamNameMapTests { readonly StreamNameMap _sut = new(); - [Fact] - public void Should_get_stream_name_for_id() { + [Test] + public async Task Should_get_stream_name_for_id() { var idString = GetId(); var id = new BookingId(idString); var streamName = StreamNameFactory.For(id); - streamName.ToString().Should().Be($"Booking-{idString}"); + await Assert.That(streamName.ToString()).IsEqualTo($"Booking-{idString}"); } - [Fact] - public void Should_get_default_stream_name_for_id() { + [Test] + public async Task Should_get_default_stream_name_for_id() { var idString = GetId(); var id = new BookingId(idString); var streamName = _sut.GetStreamName(id); - streamName.ToString().Should().Be($"Booking-{idString}"); + await Assert.That(streamName.ToString()).IsEqualTo($"Booking-{idString}"); } } diff --git a/src/Core/test/Eventuous.Tests/StreamNameTests.cs b/src/Core/test/Eventuous.Tests/StreamNameTests.cs index 6344cd63d..3b65d1f32 100644 --- a/src/Core/test/Eventuous.Tests/StreamNameTests.cs +++ b/src/Core/test/Eventuous.Tests/StreamNameTests.cs @@ -5,26 +5,26 @@ namespace Eventuous.Tests; using static Fixtures.IdGenerator; public class StreamNameTests { - [Fact] - public void Should_get_stream_name_for_state() { + [Test] + public async Task Should_get_stream_name_for_state() { var idString = GetId(); var streamName = StreamName.ForState(idString); - streamName.ToString().Should().Be($"Booking-{idString}"); + await Assert.That(streamName.ToString()).IsEqualTo($"Booking-{idString}"); } - [Fact] - public void Should_fail_when_id_is_null() { - Assert.Throws(() => _ = StreamName.For(null!)); + [Test] + public async Task Should_fail_when_id_is_null() { + await Assert.That(() => _ = StreamName.For(null!)).Throws(); } - [Fact] - public void Should_fail_when_id_is_empty() { - Assert.Throws(() => _ = StreamName.For(" ")); + [Test] + public async Task Should_fail_when_id_is_empty() { + await Assert.That(() => _ = StreamName.For(" ")).Throws(); } - [Fact] - public void Should_trim_id() { + [Test] + public async Task Should_trim_id() { var streamName = StreamName.ForState(" 123"); - streamName.ToString().Should().Be("Booking-123"); + await Assert.That(streamName.ToString()).IsEqualTo("Booking-123"); } } diff --git a/src/Core/test/Eventuous.Tests/TypeRegistrationTests.cs b/src/Core/test/Eventuous.Tests/TypeRegistrationTests.cs index a9c247f76..6c056f48a 100644 --- a/src/Core/test/Eventuous.Tests/TypeRegistrationTests.cs +++ b/src/Core/test/Eventuous.Tests/TypeRegistrationTests.cs @@ -7,9 +7,9 @@ public class TypeRegistrationTests { public TypeRegistrationTests() => _typeMapper.RegisterKnownEventTypes(typeof(BookingCancelled).Assembly); - [Fact] - public void ShouldResolveDecoratedEvent() { - _typeMapper.GetTypeName().Should().Be(TypeNames.BookingCancelled); - _typeMapper.GetType(TypeNames.BookingCancelled).Should().Be(); + [Test] + public async Task ShouldResolveDecoratedEvent() { + await Assert.That(_typeMapper.GetTypeName()).IsEqualTo(TypeNames.BookingCancelled); + await Assert.That(_typeMapper.GetType(TypeNames.BookingCancelled)).IsEqualTo(typeof(BookingCancelled)); } } \ No newline at end of file diff --git a/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Eventuous.Tests.OpenTelemetry.csproj b/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Eventuous.Tests.OpenTelemetry.csproj index cb503d315..ed473b928 100644 --- a/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Eventuous.Tests.OpenTelemetry.csproj +++ b/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Eventuous.Tests.OpenTelemetry.csproj @@ -1,5 +1,6 @@ + false true true true @@ -10,9 +11,12 @@ + + + @@ -31,4 +35,8 @@ + + + + diff --git a/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Fakes/TestHandler.cs b/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Fakes/TestHandler.cs index cab09ff32..c70f11e60 100644 --- a/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Fakes/TestHandler.cs +++ b/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Fakes/TestHandler.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.Logging; + namespace Eventuous.Tests.OpenTelemetry.Fakes; class TestHandler(MessageCounter counter, ILogger log) : BaseEventHandler { diff --git a/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Fixtures/MetricsSubscriptionFixtureBase.cs b/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Fixtures/MetricsSubscriptionFixtureBase.cs index e9b29db41..12e8d36ee 100644 --- a/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Fixtures/MetricsSubscriptionFixtureBase.cs +++ b/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/Fixtures/MetricsSubscriptionFixtureBase.cs @@ -1,6 +1,3 @@ -// Copyright (C) Ubiquitous AS.All rights reserved -// Licensed under the Apache License, Version 2.0. - using DotNet.Testcontainers.Containers; using Eventuous.Diagnostics; using Eventuous.Subscriptions.Registrations; @@ -11,13 +8,25 @@ namespace Eventuous.Tests.OpenTelemetry.Fixtures; -public abstract class MetricsSubscriptionFixtureBase : StoreFixtureBase +public interface IMetricsSubscriptionFixtureBase { + StreamName Stream { get; } + MessageCounter Counter { get; } + TestExporter Exporter { get; } + int Count { get; } + public string DefaultTagKey { get; } + public string DefaultTagValue { get; } + string SubscriptionId { get; } + IProducer Producer { get; } + IFixture Auto { get; } +} + +public abstract class MetricsSubscriptionFixtureBase : StoreFixtureBase, IMetricsSubscriptionFixtureBase where TContainer : DockerContainer where TProducer : class, IProducer where TSubscription : EventSubscriptionWithCheckpoint, IMeasuredSubscription where TSubscriptionOptions : SubscriptionWithCheckpointOptions { // ReSharper disable once ConvertToConstant.Global - public readonly int Count = 100; + public int Count => 100; // ReSharper disable once StaticMemberInGenericType static readonly KeyValuePair DefaultTag = new("test", "foo"); @@ -35,16 +44,14 @@ static MetricsSubscriptionFixtureBase() { public string DefaultTagValue => DefaultTag.Value; // ReSharper disable once ConvertToConstant.Global - public readonly string SubscriptionId = "test-sub"; + public string SubscriptionId => "test-sub"; TestListener? _listener; protected abstract void ConfigureSubscription(TSubscriptionOptions options); protected override void SetupServices(IServiceCollection services) { - if (Output != null) { - _listener = new(Output); - } + _listener = new(); services.AddProducer(); services.AddSingleton(); @@ -66,17 +73,17 @@ protected override void GetDependencies(IServiceProvider provider) { Counter = provider.GetRequiredService(); } - public TProducer Producer { get; private set; } = null!; + public IProducer Producer { get; private set; } = null!; public MessageCounter Counter { get; private set; } = null!; public TestExporter Exporter { get; } = new(); - public override async Task DisposeAsync() { + public override async ValueTask DisposeAsync() { await base.DisposeAsync(); Exporter.Dispose(); _listener?.Dispose(); } } -class TestListener(ITestOutputHelper output) : GenericListener(SubscriptionMetrics.ListenerName) { - protected override void OnEvent(KeyValuePair obj) => output.WriteLine($"{obj.Key} {obj.Value}"); +class TestListener() : GenericListener(SubscriptionMetrics.ListenerName) { + protected override void OnEvent(KeyValuePair obj) => TestContext.Current?.OutputWriter.WriteLine($"{obj.Key} {obj.Value}"); } diff --git a/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/MetricsTests.cs b/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/MetricsTests.cs index 273aeddc6..5bf7a10f9 100644 --- a/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/MetricsTests.cs +++ b/src/Diagnostics/test/Eventuous.Tests.OpenTelemetry/MetricsTests.cs @@ -1,72 +1,66 @@ -using DotNet.Testcontainers.Containers; +using Eventuous.TestHelpers.TUnit; using Eventuous.Tests.OpenTelemetry.Fakes; using Eventuous.Tests.Subscriptions.Base; +// ReSharper disable MethodHasAsyncOverload // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract namespace Eventuous.Tests.OpenTelemetry; -public abstract class MetricsTestsBase(ITestOutputHelper outputHelper) : IAsyncLifetime - where T : MetricsSubscriptionFixtureBase, new() - where TContainer : DockerContainer - where TProducer : class, IProducer - where TSubscription : EventSubscriptionWithCheckpoint, IMeasuredSubscription - where TSubscriptionOptions : SubscriptionWithCheckpointOptions { - T Fixture { get; } = new() { Output = outputHelper }; - - [Fact] - [Trait("Category", "Diagnostics")] - public void ShouldMeasureSubscriptionGapCount() { - Fixture.Output?.WriteLine($"Stream {Fixture.Stream}"); - Assert.NotNull(_values); - var gapCount = GetValue(_values, SubscriptionMetrics.GapCountMetricName)!; - var expectedGap = Fixture.Count - Fixture.Counter.Count; +public abstract class MetricsTestsBase(IMetricsSubscriptionFixtureBase fixture) { + [Test] + [Retry(3)] + public async Task ShouldMeasureSubscriptionGapCountBase() { + TestContext.Current?.OutputWriter.WriteLine($"Stream {fixture.Stream}"); + await Assert.That(_values).IsNotNull(); + var gapCount = GetValue(_values!, SubscriptionMetrics.GapCountMetricName)!; + var expectedGap = fixture.Count - fixture.Counter.Count; gapCount.Should().NotBeNull(); gapCount.Value.Should().BeInRange(expectedGap - 20, expectedGap + 20); - gapCount.CheckTag(SubscriptionMetrics.SubscriptionIdTag, Fixture.SubscriptionId); - gapCount.CheckTag(Fixture.DefaultTagKey, Fixture.DefaultTagValue); + gapCount.CheckTag(SubscriptionMetrics.SubscriptionIdTag, fixture.SubscriptionId); + gapCount.CheckTag(fixture.DefaultTagKey, fixture.DefaultTagValue); } // [Fact] // [Trait("Category", "Diagnostics")] - public void ShouldMeasureSubscriptionDuration() { - Fixture.Output?.WriteLine($"Stream {Fixture.Stream}"); - Assert.NotNull(_values); - var duration = GetValue(_values, SubscriptionMetrics.ProcessingRateName)!; - - duration.Should().NotBeNull(); - duration.CheckTag(SubscriptionMetrics.SubscriptionIdTag, Fixture.SubscriptionId); - duration.CheckTag(Fixture.DefaultTagKey, Fixture.DefaultTagValue); - duration.CheckTag(SubscriptionMetrics.MessageTypeTag, TestEvent.TypeName); - } + // public void ShouldMeasureSubscriptionDuration() { + // Fixture.Output?.WriteLine($"Stream {Fixture.Stream}"); + // Assert.NotNull(_values); + // var duration = GetValue(_values, SubscriptionMetrics.ProcessingRateName)!; + // + // duration.Should().NotBeNull(); + // duration.CheckTag(SubscriptionMetrics.SubscriptionIdTag, Fixture.SubscriptionId); + // duration.CheckTag(Fixture.DefaultTagKey, Fixture.DefaultTagValue); + // duration.CheckTag(SubscriptionMetrics.MessageTypeTag, TestEvent.TypeName); + // } static MetricValue? GetValue(MetricValue[] values, string metric) => values.FirstOrDefault(x => x.Name == metric); + [Before(Test)] public async Task InitializeAsync() { - await Fixture.InitializeAsync(); - var testEvents = Fixture.Auto.CreateMany(Fixture.Count).ToList(); - await Fixture.Producer.Produce(Fixture.Stream, testEvents, new Metadata()); + var testEvents = fixture.Auto.CreateMany(fixture.Count).ToList(); + await fixture.Producer.Produce(fixture.Stream, testEvents, new Metadata()); - while (Fixture.Counter.Count < Fixture.Count / 2) { + while (fixture.Counter.Count < fixture.Count / 2) { await Task.Delay(100); } - Fixture.Exporter.Collect(Timeout.Infinite); - _values = Fixture.Exporter.CollectValues(); + fixture.Exporter.Collect(Timeout.Infinite); + _values = fixture.Exporter.CollectValues(); foreach (var value in _values) { - Fixture.Output?.WriteLine(value.ToString()); + TestContext.Current?.OutputWriter.WriteLine(value.ToString()); } } - public async Task DisposeAsync() { - await Fixture.DisposeAsync(); + [After(Test)] + public void Teardown() { _es.Dispose(); } - readonly TestEventListener _es = new(outputHelper, null, "OpenTelemetry"); + readonly TestEventListener _es = new(null, "OpenTelemetry"); MetricValue[]? _values; } diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 875b45537..f3815edd2 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,6 +1,6 @@ - net6.0;net8.0 + net9.0;net8.0 preview $(NoWarn);CS1591;CS0618; enable @@ -21,11 +21,17 @@ true false + trx%3bLogFileName=$(MSBuildProjectName).trx $(RepoRoot)/test-results/$(TargetFramework) false true + true + true + true + --report-trx --results-directory $(RepoRoot)/test-results/$(TargetFramework) + false true @@ -56,21 +62,16 @@ - - - - - + + - - diff --git a/src/Directory.Testable.targets b/src/Directory.Testable.targets index 890dcb454..cfb8cf129 100644 --- a/src/Directory.Testable.targets +++ b/src/Directory.Testable.targets @@ -1,8 +1,11 @@ - - + + false CA1816 + + + diff --git a/src/EventStore/src/Eventuous.EventStore/Subscriptions/EventStoreCatchUpSubscriptionBase.cs b/src/EventStore/src/Eventuous.EventStore/Subscriptions/EventStoreCatchUpSubscriptionBase.cs index a3e3b9233..0a897d32f 100644 --- a/src/EventStore/src/Eventuous.EventStore/Subscriptions/EventStoreCatchUpSubscriptionBase.cs +++ b/src/EventStore/src/Eventuous.EventStore/Subscriptions/EventStoreCatchUpSubscriptionBase.cs @@ -47,8 +47,8 @@ protected EventStoreCatchUpSubscriptionBase( /// protected override async ValueTask Unsubscribe(CancellationToken cancellationToken) { try { - Subscription?.Dispose(); Stopping.Cancel(false); + Subscription?.Dispose(); await Task.Delay(100, cancellationToken); } catch (Exception) { // Nothing to see here diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/AppServiceTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/AppServiceTests.cs index a2a842985..c227fc3c1 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/AppServiceTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/AppServiceTests.cs @@ -1,43 +1,43 @@ using Eventuous.Sut.App; using Eventuous.Sut.Domain; -using Eventuous.TestHelpers; +using Eventuous.TestHelpers.TUnit; namespace Eventuous.Tests.EventStore; -public class AppServiceTests : IClassFixture, IDisposable { - readonly TestEventListener _listener; +[ClassDataSource(Shared = SharedType.None)] +public class AppServiceTests { + readonly TestEventListener _listener = new(); readonly StoreFixture _fixture; - public AppServiceTests(StoreFixture fixture, ITestOutputHelper output) { - _fixture = fixture; - _listener = new(output); - Service = new(fixture.EventStore); + public AppServiceTests(StoreFixture fixture) { + _fixture = fixture; _fixture.TypeMapper.AddType(); } - BookingService Service { get; } + BookingService Service { get; set; } = null!; - [Fact] - [Trait("Category", "Application")] - public async Task ProcessAnyForNew() { + [Before(Test)] + public void BeforeTest() { + Service = new(_fixture.EventStore); + } + + [Test] + [Category("Application")] + public async Task ProcessAnyForNew(CancellationToken cancellationToken) { var cmd = DomainFixture.CreateImportBooking(); var expected = new object[] { new BookingEvents.BookingImported(cmd.RoomId, cmd.Price, cmd.CheckIn, cmd.CheckOut) }; - var handlingResult = await Service.Handle(cmd, default); + var handlingResult = await Service.Handle(cmd, cancellationToken); handlingResult.Success.Should().BeTrue(); - var events = await _fixture.EventStore.ReadEvents( - StreamName.For(cmd.BookingId), - StreamReadPosition.Start, - int.MaxValue, - default - ); + var events = await _fixture.EventStore.ReadEvents(StreamName.For(cmd.BookingId), StreamReadPosition.Start, int.MaxValue, cancellationToken); var result = events.Select(x => x.Payload).ToArray(); result.Should().BeEquivalentTo(expected); } + [After(Test)] public void Dispose() => _listener.Dispose(); } diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Eventuous.Tests.EventStore.csproj b/src/EventStore/test/Eventuous.Tests.EventStore/Eventuous.Tests.EventStore.csproj index 12980d371..bac49f0a3 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Eventuous.Tests.EventStore.csproj +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Eventuous.Tests.EventStore.csproj @@ -3,6 +3,7 @@ true true true + Exe @@ -17,6 +18,8 @@ + + diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Fixtures/DomainFixture.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Fixtures/DomainFixture.cs index d638f1d7b..d8a039f47 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Fixtures/DomainFixture.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Fixtures/DomainFixture.cs @@ -1,10 +1,11 @@ using Eventuous.Sut.App; +using Eventuous.Sut.Domain; using MicroElements.AutoFixture.NodaTime; namespace Eventuous.Tests.EventStore.Fixtures; public static class DomainFixture { - static DomainFixture() => TypeMap.RegisterKnownEventTypes(); + static DomainFixture() => TypeMap.RegisterKnownEventTypes(typeof(BookingEvents.BookingImported).Assembly); static IFixture Auto { get; } = new Fixture().Customize(new NodaTimeCustomization()); diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Fixtures/EsdbContainer.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Fixtures/EsdbContainer.cs index d904e850d..7b02ed3eb 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Fixtures/EsdbContainer.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Fixtures/EsdbContainer.cs @@ -6,8 +6,8 @@ namespace Eventuous.Tests.EventStore.Fixtures; public static class EsdbContainer { public static EventStoreDbContainer Create() { var image = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 - ? "eventstore/eventstore:24.2.0-alpha-arm64v8" - : "eventstore/eventstore:24.2"; + ? "eventstore/eventstore:24.6.0-alpha-arm64v8" + : "eventstore/eventstore:24.6"; return new EventStoreDbBuilder() .WithImage(image) diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Limiter.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Limiter.cs new file mode 100644 index 000000000..7a9625204 --- /dev/null +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Limiter.cs @@ -0,0 +1,10 @@ +using Eventuous.Tests.EventStore; +using TUnit.Core.Interfaces; + +[assembly: ParallelLimiter] + +namespace Eventuous.Tests.EventStore; + +public class Limiter : IParallelLimit { + public int Limit => 4; +} diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Metrics/MetricsTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Metrics/MetricsTests.cs index 3ae28d16d..ac1415af3 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Metrics/MetricsTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Metrics/MetricsTests.cs @@ -1,11 +1,7 @@ -using Eventuous.EventStore.Producers; -using Eventuous.EventStore.Subscriptions; using Eventuous.Tests.OpenTelemetry; -using Testcontainers.EventStoreDb; -// ReSharper disable UnusedType.Global namespace Eventuous.Tests.EventStore.Metrics; -[Collection("Database")] -public class MetricsTests(ITestOutputHelper outputHelper) - : MetricsTestsBase(outputHelper); +[ClassDataSource] +[InheritsTests] +public class MetricsTests(MetricsFixture fixture) : MetricsTestsBase(fixture); diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/ProducerTracesTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/ProducerTracesTests.cs index 0b1632bfe..0d94a7f3c 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/ProducerTracesTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/ProducerTracesTests.cs @@ -7,12 +7,12 @@ namespace Eventuous.Tests.EventStore; -public class TracesTests : LegacySubscriptionFixture, IDisposable { +public class TracesTests : LegacySubscriptionFixture { readonly ActivityListener _listener; static TracesTests() => TypeMap.Instance.AddType(TestEvent.TypeName); - public TracesTests(ITestOutputHelper output) : base(output, new(), false) { + public TracesTests() : base(new()) { _listener = new() { ShouldListenTo = _ => true, Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, @@ -28,16 +28,16 @@ public class TracesTests : LegacySubscriptionFixture, IDisposable ActivitySource.AddActivityListener(_listener); } - [Fact] - [Trait("Category", "Diagnostics")] - public async Task ShouldPropagateRemoteContext() { + [Test] + [Category("Diagnostics")] + public async Task ShouldPropagateRemoteContext(CancellationToken cancellationToken) { var testEvent = Auto.Create(); - await Producer.Produce(Stream, testEvent, new()); + await Producer.Produce(Stream, testEvent, new(), cancellationToken: cancellationToken); await Start(); - var writtenEvent = (await StoreFixture.EventStore.ReadEvents(Stream, StreamReadPosition.Start, 1, default))[0]; + var writtenEvent = (await StoreFixture.EventStore.ReadEvents(Stream, StreamReadPosition.Start, 1, cancellationToken))[0]; var meta = writtenEvent.Metadata; var (traceId, spanId, _) = meta.GetTracingMeta(); @@ -46,7 +46,7 @@ public async Task ShouldPropagateRemoteContext() { spanId.Should().NotBe(RecordedTrace.DefaultSpanId); while (Handler.Contexts.Count == 0) { - await Task.Delay(100); + await Task.Delay(100, cancellationToken); } await Stop(); @@ -61,5 +61,6 @@ public async Task ShouldPropagateRemoteContext() { recordedTrace.ParentSpanId!.Value.ToString().Should().Be(spanId); } + [After(Test)] public void Dispose() => _listener.Dispose(); } diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/RegistrationTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/RegistrationTests.cs index 0a712fbfa..40aa88b65 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/RegistrationTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/RegistrationTests.cs @@ -7,7 +7,8 @@ namespace Eventuous.Tests.EventStore; -public class RegistrationTests(StoreFixture fixture) : IClassFixture, IAsyncLifetime { +[ClassDataSource] +public class RegistrationTests(StoreFixture fixture) { const string SubId = "Test"; static readonly StreamName Stream = new("teststream"); @@ -15,34 +16,35 @@ public class RegistrationTests(StoreFixture fixture) : IClassFixture(); } - [Fact] - [Trait("Category", "Dependency injection")] + [Test] + [Category("Dependency injection")] public void ShouldHaveProperId() => Sub.SubscriptionId.Should().Be(SubId); - [Fact] - [Trait("Category", "Dependency injection")] + [Test] + [Category("Dependency injection")] public void ShouldHaveEventStoreClient() { var client = Sub.GetPrivateMember("EventStoreClient"); client.Should().Be(fixture.Client); } - [Fact] - [Trait("Category", "Dependency injection")] + [Test] + [Category("Dependency injection")] public void ShouldHaveNoOpStore() { var store = Sub.GetPrivateMember("CheckpointStore"); store.Should().BeOfType(); } - public Task InitializeAsync() { + [Before(Test)] + public ValueTask InitializeAsync() { var services = new ServiceCollection(); services.AddSingleton(fixture.Client); @@ -59,10 +61,11 @@ public Task InitializeAsync() { Provider = services.BuildServiceProvider(); Sub = Provider.GetService()!; - return Task.CompletedTask; + return ValueTask.CompletedTask; } - public Task DisposeAsync() => Task.CompletedTask; + [After(Test)] + public ValueTask DisposeAsync() => ValueTask.CompletedTask; } public class TestHandler : BaseEventHandler { diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Store/AggregateStoreTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Store/AggregateStoreTests.cs index 438bce6fc..b574f4102 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Store/AggregateStoreTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Store/AggregateStoreTests.cs @@ -1,34 +1,36 @@ using System.Collections.Immutable; using JetBrains.Annotations; using static Eventuous.AggregateFactoryRegistry; +using LoggingExtensions = Eventuous.TestHelpers.TUnit.Logging.LoggingExtensions; namespace Eventuous.Tests.EventStore.Store; -public class AggregateStoreTests : IClassFixture { +[ClassDataSource] +public class AggregateStoreTests { readonly StoreFixture _fixture; readonly ILogger _log; - public AggregateStoreTests(StoreFixture fixture, ITestOutputHelper output) { + public AggregateStoreTests(StoreFixture fixture) { _fixture = fixture; _fixture.TypeMapper.AddType("testAggregateEvent"); - var loggerFactory = LoggerFactory.Create(cfg => cfg.AddXunit(output).SetMinimumLevel(LogLevel.Debug)); + var loggerFactory = LoggingExtensions.GetLoggerFactory(); _log = loggerFactory.CreateLogger(); } - [Fact] - [Trait("Category", "Store")] + [Test] + [Category("Store")] [Obsolete("Obsolete")] - public async Task AppendedEventShouldBeTraced() { + public async Task AppendedEventShouldBeTraced(CancellationToken cancellationToken) { var id = new TestId(Guid.NewGuid().ToString("N")); var aggregate = Instance.CreateInstance(); aggregate.DoIt("test"); - await _fixture.AggregateStore.Store(aggregate, id, CancellationToken.None); + await _fixture.AggregateStore.Store(aggregate, id, cancellationToken); } - [Fact] - [Trait("Category", "Store")] + [Test] + [Category("Store")] [Obsolete("Obsolete")] - public async Task ShouldReadLongAggregateStream() { + public async Task ShouldReadLongAggregateStream(CancellationToken cancellationToken) { const int count = 9000; var id = new TestId(Guid.NewGuid().ToString("N")); @@ -43,33 +45,33 @@ public async Task ShouldReadLongAggregateStream() { if (counter != 1000) continue; _log.LogInformation("Storing batch of events.."); - await _fixture.AggregateStore.Store(aggregate, id, CancellationToken.None); - aggregate = await _fixture.AggregateStore.Load(id, CancellationToken.None); + await _fixture.AggregateStore.Store(aggregate, id, cancellationToken); + aggregate = await _fixture.AggregateStore.Load(id, cancellationToken); counter = 0; } - await _fixture.AggregateStore.Store(aggregate, id, CancellationToken.None); + await _fixture.AggregateStore.Store(aggregate, id, cancellationToken); _log.LogInformation("Loading large aggregate stream.."); - var restored = await _fixture.AggregateStore.Load(id, CancellationToken.None); + var restored = await _fixture.AggregateStore.Load(id, cancellationToken); restored.State.Values.Count.Should().Be(count); restored.State.Values.Should().BeEquivalentTo(aggregate.State.Values); } - [Fact] - [Trait("Category", "Store")] + [Test] + [Category("Store")] [Obsolete("Obsolete")] - public async Task ShouldReadAggregateStreamManyTimes() { + public async Task ShouldReadAggregateStreamManyTimes(CancellationToken cancellationToken) { var id = new TestId(Guid.NewGuid().ToString("N")); var aggregate = Instance.CreateInstance(); aggregate.DoIt("test"); - await _fixture.AggregateStore.Store(aggregate, id, default); + await _fixture.AggregateStore.Store(aggregate, id, cancellationToken); const int numberOfReads = 100; foreach (var unused in Enumerable.Range(0, numberOfReads)) { - var read = await _fixture.AggregateStore.Load(id, default); + var read = await _fixture.AggregateStore.Load(id, cancellationToken); read.State.Should().BeEquivalentTo(aggregate.State); } } diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Store/EventStoreAggregateTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Store/EventStoreAggregateTests.cs index 90d9c87cf..6ff7af8c2 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Store/EventStoreAggregateTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Store/EventStoreAggregateTests.cs @@ -1,40 +1,42 @@ using System.Collections.Immutable; using JetBrains.Annotations; using static Eventuous.AggregateFactoryRegistry; +using LoggingExtensions = Eventuous.TestHelpers.TUnit.Logging.LoggingExtensions; namespace Eventuous.Tests.EventStore.Store; -public class EventStoreAggregateTests : IClassFixture, IDisposable { +[ClassDataSource] +public class EventStoreAggregateTests { readonly StoreFixture _fixture; readonly ILogger _log; readonly ILoggerFactory _loggerFactory; - public EventStoreAggregateTests(StoreFixture fixture, ITestOutputHelper output) { + public EventStoreAggregateTests(StoreFixture fixture) { _fixture = fixture; _fixture.TypeMapper.AddType("testEvent"); - _loggerFactory = LoggerFactory.Create(cfg => cfg.AddXunit(output).SetMinimumLevel(LogLevel.Debug)); + _loggerFactory = LoggingExtensions.GetLoggerFactory(); _log = _loggerFactory.CreateLogger(); } - [Fact] - [Trait("Category", "Store")] - public async Task AppendedEventShouldBeTraced() { + [Test] + [Category("Store")] + public async Task AppendedEventShouldBeTraced(CancellationToken cancellationToken) { var id = new TestId(Guid.NewGuid().ToString("N")); var aggregate = Instance.CreateInstance(); aggregate.DoIt("test"); - await _fixture.EventStore.StoreAggregate(aggregate, id); + await _fixture.EventStore.StoreAggregate(aggregate, id, cancellationToken: cancellationToken); var streamName = StreamNameFactory.For(id); - var events = await _fixture.EventStore.ReadStream(streamName, StreamReadPosition.Start); + var events = await _fixture.EventStore.ReadStream(streamName, StreamReadPosition.Start, cancellationToken: cancellationToken); var first = events[0]; first.Metadata["trace-id"].Should().NotBeNull(); first.Metadata["span-id"].Should().NotBeNull(); } - [Fact] - [Trait("Category", "Store")] - public async Task ShouldReadLongAggregateStream() { + [Test] + [Category("Store")] + public async Task ShouldReadLongAggregateStream(CancellationToken cancellationToken) { const int count = 9000; var id = new TestId(Guid.NewGuid().ToString("N")); @@ -49,32 +51,32 @@ public async Task ShouldReadLongAggregateStream() { if (counter != 1000) continue; _log.LogInformation("Storing batch of events.."); - await _fixture.EventStore.StoreAggregate(aggregate, id); - aggregate = await _fixture.EventStore.LoadAggregate(id); + await _fixture.EventStore.StoreAggregate(aggregate, id, cancellationToken: cancellationToken); + aggregate = await _fixture.EventStore.LoadAggregate(id, cancellationToken: cancellationToken); counter = 0; } - await _fixture.EventStore.StoreAggregate(aggregate, id); + await _fixture.EventStore.StoreAggregate(aggregate, id, cancellationToken: cancellationToken); _log.LogInformation("Loading large aggregate stream.."); - var restored = await _fixture.EventStore.LoadAggregate(id); + var restored = await _fixture.EventStore.LoadAggregate(id, cancellationToken: cancellationToken); restored.State.Values.Count.Should().Be(count); restored.State.Values.Should().BeEquivalentTo(aggregate.State.Values); } - [Fact] - [Trait("Category", "Store")] - public async Task ShouldReadAggregateStreamManyTimes() { + [Test] + [Category("Store")] + public async Task ShouldReadAggregateStreamManyTimes(CancellationToken cancellationToken) { var id = new TestId(Guid.NewGuid().ToString("N")); var aggregate = Instance.CreateInstance(); aggregate.DoIt("test"); - await _fixture.EventStore.StoreAggregate(aggregate, id); + await _fixture.EventStore.StoreAggregate(aggregate, id, cancellationToken: cancellationToken); const int numberOfReads = 100; foreach (var unused in Enumerable.Range(0, numberOfReads)) { - var read = await _fixture.EventStore.LoadAggregate(id); + var read = await _fixture.EventStore.LoadAggregate(id, cancellationToken: cancellationToken); read.State.Should().BeEquivalentTo(aggregate.State); } } @@ -94,5 +96,6 @@ class TestAggregate : Aggregate { record TestEvent(string Data); + [After(Test)] public void Dispose() => _loggerFactory.Dispose(); } diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Store/StoreTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Store/StoreTests.cs index 02a217d76..5b050bdba 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Store/StoreTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Store/StoreTests.cs @@ -4,8 +4,14 @@ namespace Eventuous.Tests.EventStore.Store; +[InheritsTests] +[ClassDataSource] public class Append(StoreFixture fixture) : StoreAppendTests(fixture); +[InheritsTests] +[ClassDataSource] public class Read(StoreFixture fixture) : StoreReadTests(fixture); +[InheritsTests] +[ClassDataSource] public class OtherMethods(StoreFixture fixture) : StoreOtherOpsTests(fixture); diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Store/TieredStoreTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Store/TieredStoreTests.cs index 22be37e2a..3da1f3dd9 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Store/TieredStoreTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Store/TieredStoreTests.cs @@ -1,12 +1,11 @@ using Eventuous.Tests.Persistence.Base.Store; -using JetBrains.Annotations; using Testcontainers.EventStoreDb; namespace Eventuous.Tests.EventStore.Store; -[UsedImplicitly] -public class TieredStoreTests(StoreFixture storeFixture) : TieredStoreTestsBase(storeFixture), IClassFixture { - [Fact] +[ClassDataSource] +public class TieredStoreTests(StoreFixture storeFixture) : TieredStoreTestsBase(storeFixture) { + [Test] public async Task Esdb_should_load_hot_and_archive() { await Should_load_hot_and_archive(); } diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/Fixtures/CatchUpSubscriptionFixture.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/Fixtures/CatchUpSubscriptionFixture.cs index a334dd473..8ccc94f09 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/Fixtures/CatchUpSubscriptionFixture.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/Fixtures/CatchUpSubscriptionFixture.cs @@ -9,17 +9,11 @@ namespace Eventuous.Tests.EventStore.Subscriptions.Fixtures; public class CatchUpSubscriptionFixture( Action configureOptions, - ITestOutputHelper outputHelper, StreamName streamName, bool autoStart = true, Action? configureServices = null, LogLevel logLevel = LogLevel.Debug - ) - : SubscriptionFixtureBase( - outputHelper, - autoStart, - logLevel - ) + ) : SubscriptionFixtureBase(autoStart, logLevel) where TSubscription : EventStoreCatchUpSubscriptionBase where TSubscriptionOptions : CatchUpSubscriptionOptions where TEventHandler : class, IEventHandler { @@ -33,7 +27,7 @@ protected override void SetupServices(IServiceCollection services) { base.SetupServices(services); services.AddEventStoreClient(Container.GetConnectionString()); services.AddEventStore(); - services.AddSingleton(new TestEventHandlerOptions(null, _outputHelper)); + services.AddSingleton(new TestEventHandlerOptions()); configureServices?.Invoke(services); } @@ -48,7 +42,7 @@ protected override void GetDependencies(IServiceProvider provider) { public override async Task GetLastPosition() { return streamName == "$all" ? await GetLastFromAll() : await GetLastFromStream(); - + async Task GetLastFromStream() { var lastEvent = await Client.ReadStreamAsync(Direction.Backwards, streamName, StreamPosition.End, 1).ToArrayAsync(); @@ -68,8 +62,6 @@ async Task GetLastFromAll() { "Grpc.Net.Client.Internal.GrpcCall" ]; - readonly ITestOutputHelper _outputHelper = outputHelper; - static bool Filter(string? provider, string? category, LogLevel logLevel) { if (category == null) return true; diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/Fixtures/LegacySubscriptionFixture.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/Fixtures/LegacySubscriptionFixture.cs index 7e3f9a889..e33ed9200 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/Fixtures/LegacySubscriptionFixture.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/Fixtures/LegacySubscriptionFixture.cs @@ -2,10 +2,12 @@ using Eventuous.EventStore.Subscriptions; using Eventuous.Subscriptions.Filters; using Eventuous.Tests.Subscriptions.Base; +using TUnit.Core.Interfaces; +using LoggingExtensions = Eventuous.TestHelpers.TUnit.Logging.LoggingExtensions; namespace Eventuous.Tests.EventStore.Subscriptions.Fixtures; -public abstract class LegacySubscriptionFixture : IAsyncLifetime where T : class, IEventHandler { +public abstract class LegacySubscriptionFixture: IAsyncInitializer, IAsyncDisposable where T : class, IEventHandler { protected readonly Fixture Auto = new(); protected StreamName Stream { get; } = new($"test-{Guid.NewGuid():N}"); @@ -16,17 +18,10 @@ public abstract class LegacySubscriptionFixture : IAsyncLifetime where T : cl protected TestCheckpointStore CheckpointStore { get; } = new(); protected StreamSubscription Subscription { get; set; } = null!; - protected LegacySubscriptionFixture( - ITestOutputHelper output, - T handler, - bool autoStart = true, - StreamName? stream = null, - LogLevel logLevel = LogLevel.Debug - ) { - _autoStart = autoStart; + protected LegacySubscriptionFixture(T handler, StreamName? stream = null, LogLevel logLevel = LogLevel.Debug) { if (stream is { } s) Stream = s; - LoggerFactory = TestHelpers.Logging.GetLoggerFactory(output, logLevel); + LoggerFactory = LoggingExtensions.GetLoggerFactory(logLevel); Handler = handler; Log = LoggerFactory.CreateLogger(GetType()); StoreFixture.TypeMapper.RegisterKnownEventTypes(typeof(TestEvent).Assembly); @@ -37,8 +32,6 @@ protected LegacySubscriptionFixture( protected ValueTask Stop() => Subscription.UnsubscribeWithLog(Log); ILoggerFactory LoggerFactory { get; } - readonly bool _autoStart; - public async Task InitializeAsync() { await StoreFixture.InitializeAsync(); Producer = new(StoreFixture.Client); @@ -58,11 +51,23 @@ public async Task InitializeAsync() { pipe, LoggerFactory ); - if (_autoStart) await Start(); } - public async Task DisposeAsync() { - if (_autoStart) await Stop(); + public async ValueTask DisposeAsync() { await StoreFixture.DisposeAsync(); } } + +public class LegacySubscriptionFixture(TimeSpan? timeout, bool autoStart = true, StreamName? stream = null, LogLevel logLevel = LogLevel.Debug) + : LegacySubscriptionFixture(new(new(timeout)), stream, logLevel) { + [Before(Test)] + public async Task Setup() { + await InitializeAsync(); + if (autoStart) await Start(); + } + + public async Task Teardown() { + if (autoStart) await Stop(); + await DisposeAsync(); + } +} diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/Fixtures/PersistentSubscriptionFixture.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/Fixtures/PersistentSubscriptionFixture.cs index 62bfb06ab..3b2769713 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/Fixtures/PersistentSubscriptionFixture.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/Fixtures/PersistentSubscriptionFixture.cs @@ -2,51 +2,49 @@ using Eventuous.EventStore.Producers; using Eventuous.EventStore.Subscriptions; using Eventuous.Tests.Subscriptions.Base; +using LoggingExtensions = Eventuous.TestHelpers.TUnit.Logging.LoggingExtensions; namespace Eventuous.Tests.EventStore.Subscriptions.Fixtures; -public abstract class PersistentSubscriptionFixture( - ITestOutputHelper outputHelper, - THandler handler, - bool autoStart = true, - LogLevel logLevel = LogLevel.Information - ) : IAsyncLifetime +public class PersistentSubscriptionFixture( + THandler handler, + Func subscriptionFactory, + bool autoStart = true, + LogLevel logLevel = LogLevel.Information + ) where THandler : class, IEventHandler where TSubscription : PersistentSubscriptionBase where TOptions : PersistentSubscriptionOptions { + public readonly Fixture Auto = new(); - protected readonly Fixture Auto = new(); - - protected StreamName Stream { get; } = new($"test-{Guid.NewGuid():N}"); - protected THandler Handler { get; } = handler; - protected EventStoreProducer Producer { get; private set; } = null!; + public StreamName Stream { get; } = new($"test-{Guid.NewGuid():N}"); + public THandler Handler { get; } = handler; + public EventStoreProducer Producer { get; private set; } = null!; protected ILogger Log { get; set; } = null!; protected StoreFixture Fixture { get; } = new(); TSubscription Subscription { get; set; } = null!; - protected ValueTask Start() => Subscription.SubscribeWithLog(Log); + public ValueTask Start() => Subscription.SubscribeWithLog(Log); - protected ValueTask Stop() => Subscription.UnsubscribeWithLog(Log); + public ValueTask Stop() => Subscription.UnsubscribeWithLog(Log); LoggingEventListener _listener = null!; - protected abstract TSubscription CreateSubscription(string id, ILoggerFactory loggerFactory); - - public async Task InitializeAsync() { + public async ValueTask InitializeAsync() { Fixture.TypeMapper.RegisterKnownEventTypes(typeof(TestEvent).Assembly); await Fixture.InitializeAsync(); Producer = new(Fixture.Client); - var loggerFactory = TestHelpers.Logging.GetLoggerFactory(outputHelper, logLevel); + var loggerFactory = LoggingExtensions.GetLoggerFactory(logLevel); var subscriptionId = $"test-{Guid.NewGuid():N}"; Log = loggerFactory.CreateLogger(GetType()); _listener = new(loggerFactory); - Subscription = CreateSubscription(subscriptionId, loggerFactory); + Subscription = subscriptionFactory(subscriptionId, Fixture.Container.GetConnectionString(), Stream, Handler, loggerFactory); if (autoStart) await Start(); } - public async Task DisposeAsync() { + public async ValueTask DisposeAsync() { if (autoStart) await Stop(); _listener.Dispose(); } diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/PublishAndSubscribeManyPartitionedTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/PublishAndSubscribeManyPartitionedTests.cs index 4497a0017..135930e3a 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/PublishAndSubscribeManyPartitionedTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/PublishAndSubscribeManyPartitionedTests.cs @@ -4,18 +4,15 @@ namespace Eventuous.Tests.EventStore.Subscriptions; -[Collection("Database")] -public class PublishAndSubscribeManyPartitionedTests(ITestOutputHelper output) - : LegacySubscriptionFixture( - output, - new(new(5.Milliseconds(), output)), +public class PublishAndSubscribeManyPartitionedTests() : LegacySubscriptionFixture( + 5.Milliseconds(), false, new StreamName(Guid.NewGuid().ToString("N")), logLevel: LogLevel.Trace ) { - [Fact] - [Trait("Category", "Stream catch-up subscription")] - public async Task SubscribeAndProduceMany() { + [Test] + [Category("Stream catch-up subscription")] + public async Task SubscribeAndProduceMany(CancellationToken cancellationToken) { const int count = 10; var testEvents = Enumerable.Range(1, count) @@ -23,8 +20,8 @@ public async Task SubscribeAndProduceMany() { .ToList(); await Start(); - await Producer.Produce(Stream, testEvents, new Metadata()); - await Handler.AssertCollection(5.Seconds(), [..testEvents]).Validate(); + await Producer.Produce(Stream, testEvents, new Metadata(), cancellationToken: cancellationToken); + await Handler.AssertCollection(5.Seconds(), [..testEvents]).Validate(cancellationToken); await Stop(); CheckpointStore.GetCheckpoint(Subscription.SubscriptionId).Should().Be(count - 1); diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/PublishAndSubscribeManyTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/PublishAndSubscribeManyTests.cs index 062f4c21c..3865eda6e 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/PublishAndSubscribeManyTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/PublishAndSubscribeManyTests.cs @@ -4,19 +4,17 @@ namespace Eventuous.Tests.EventStore.Subscriptions; -[Collection("Database")] -public class PublishAndSubscribeManyTests(ITestOutputHelper output) - : LegacySubscriptionFixture(output, new(new(1.Milliseconds(), output)), false, logLevel: LogLevel.Debug) { - [Fact] - [Trait("Category", "Stream catch-up subscription")] - public async Task SubscribeAndProduceMany() { +public class PublishAndSubscribeManyTests() : LegacySubscriptionFixture(1.Milliseconds(), false) { + [Test] + [Category("Stream catch-up subscription")] + public async Task SubscribeAndProduceMany(CancellationToken cancellationToken) { const int count = 100; var testEvents = Auto.CreateMany(count).ToList(); await Start(); - await Producer.Produce(Stream, testEvents, new()); - await Handler.AssertCollection(10.Seconds(), [..testEvents]).Validate(); + await Producer.Produce(Stream, testEvents, new(), cancellationToken: cancellationToken); + await Handler.AssertCollection(10.Seconds(), [..testEvents]).Validate(cancellationToken); await Stop(); CheckpointStore.GetCheckpoint(Subscription.SubscriptionId).Should().Be(count - 1); diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/PublishAndSubscribeOneTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/PublishAndSubscribeOneTests.cs index 7482ddf60..e3fd05236 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/PublishAndSubscribeOneTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/PublishAndSubscribeOneTests.cs @@ -4,20 +4,18 @@ namespace Eventuous.Tests.EventStore.Subscriptions; -[Collection("Database")] -public class PublishAndSubscribeOneTests(ITestOutputHelper output) - : LegacySubscriptionFixture(output, new(new(null, output)), false, logLevel: LogLevel.Debug) { - [Fact] - [Trait("Category", "Stream catch-up subscription")] - public async Task SubscribeAndProduce() { +public class PublishAndSubscribeOneTests() : LegacySubscriptionFixture(null, false) { + [Test] + [Category("Stream catch-up subscription")] + public async Task SubscribeAndProduce(CancellationToken cancellationToken) { var testEvent = Auto.Create(); await Start(); - await Producer.Produce(Stream, testEvent, new Metadata()); - await Handler.AssertCollection(5.Seconds(), [testEvent]).Validate(); + await Producer.Produce(Stream, testEvent, new(), cancellationToken: cancellationToken); + await Handler.AssertCollection(5.Seconds(), [testEvent]).Validate(cancellationToken); await Stop(); - await Task.Delay(100); + await Task.Delay(100, cancellationToken); CheckpointStore.GetCheckpoint(Subscription.SubscriptionId).Should().Be(0); } } diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/StreamPersistentPublishAndSubscribeManyTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/StreamPersistentPublishAndSubscribeManyTests.cs index b62aaa18f..980f941e4 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/StreamPersistentPublishAndSubscribeManyTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/StreamPersistentPublishAndSubscribeManyTests.cs @@ -7,61 +7,58 @@ namespace Eventuous.Tests.EventStore.Subscriptions; -[Collection("Database")] -public class StreamPersistentPublishAndSubscribeManyTests1(ITestOutputHelper outputHelper) - : PersistentSubscriptionFixture(outputHelper, new(), false) { - [Fact] - [Trait("Category", "Persistent subscription")] - public async Task SubscribeAndProduceMany() { +public class StreamPersistentPublishAndSubscribeManyTests { + [Test] + [Category("Persistent subscription")] + [MethodDataSource(nameof(GetFixtures))] + public async Task SubscribeAndProduceMany( + PersistentSubscriptionFixture fixture, + CancellationToken cancellationToken + ) { const int count = 1000; - var testEvents = Auto.CreateMany(count).ToList(); + var testEvents = fixture.Auto.CreateMany(count).ToList(); - await Start(); - await Producer.Produce(Stream, testEvents, new()); - await Handler.AssertCollection(10.Seconds(), [..testEvents]).Validate(); - await Stop(); + await fixture.InitializeAsync(); + await fixture.Start(); + await fixture.Producer.Produce(fixture.Stream, testEvents, new(), cancellationToken: cancellationToken); + await fixture.Handler.AssertCollection(10.Seconds(), [..testEvents]).Validate(cancellationToken); + await fixture.Stop(); + await fixture.DisposeAsync(); } - protected override StreamPersistentSubscription CreateSubscription(string id, ILoggerFactory loggerFactory) - => new( - Fixture.Client, + public static PersistentSubscriptionFixture[] GetFixtures() { + return [ + new(new(), CreateWithRegularClient, false), + new(new(), CreateWithPersistentSubClient, false), + ]; + } + + static StreamPersistentSubscription CreateWithRegularClient(string id, string connectionString, StreamName stream, TestEventHandler handler, ILoggerFactory loggerFactory) { + var settings = EventStoreClientSettings.Create(connectionString); + + return new( + new EventStoreClient(settings), new() { - StreamName = Stream, + StreamName = stream, SubscriptionId = id }, - new ConsumePipe().AddDefaultConsumer(Handler), + new ConsumePipe().AddDefaultConsumer(handler), loggerFactory ); -} - -[Collection("Database")] -public class StreamPersistentPublishAndSubscribeManyTests2(ITestOutputHelper outputHelper) - : PersistentSubscriptionFixture(outputHelper, new(), false) { - [Fact] - [Trait("Category", "Persistent subscription")] - public async Task SubscribeAndProduceMany() { - const int count = 1000; - - var testEvents = Auto.CreateMany(count).ToList(); - - await Start(); - await Producer.Produce(Stream, testEvents, new()); - await Handler.AssertCollection(10.Seconds(), [..testEvents]).Validate(); - await Stop(); } - protected override StreamPersistentSubscription CreateSubscription(string id, ILoggerFactory loggerFactory) { - var connectionString = Fixture.Container.GetConnectionString(); - var settings = EventStoreClientSettings.Create(connectionString); - var client = new EventStorePersistentSubscriptionsClient(settings); + static StreamPersistentSubscription CreateWithPersistentSubClient(string id, string connectionString, StreamName stream, TestEventHandler handler, ILoggerFactory loggerFactory) { + var settings = EventStoreClientSettings.Create(connectionString); + var client = new EventStorePersistentSubscriptionsClient(settings); + return new( client, new() { - StreamName = Stream, + StreamName = stream, SubscriptionId = id }, - new ConsumePipe().AddDefaultConsumer(Handler), + new ConsumePipe().AddDefaultConsumer(handler), loggerFactory ); } diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/StreamSubscriptionDeletedEventsTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/StreamSubscriptionDeletedEventsTests.cs index ff923c53b..b027824aa 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/StreamSubscriptionDeletedEventsTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/StreamSubscriptionDeletedEventsTests.cs @@ -1,4 +1,5 @@ -using EventStore.Client; +// using EventStore.Client; + using Eventuous.Diagnostics.Logging; using Eventuous.EventStore.Subscriptions; using Eventuous.Subscriptions.Context; @@ -6,41 +7,44 @@ using Eventuous.Sut.App; using Eventuous.Sut.Domain; using Eventuous.Tests.Subscriptions.Base; +using LoggingExtensions = Eventuous.TestHelpers.TUnit.Logging.LoggingExtensions; using StreamSubscription = Eventuous.EventStore.Subscriptions.StreamSubscription; namespace Eventuous.Tests.EventStore.Subscriptions; -[Collection("Database")] -public sealed class StreamSubscriptionDeletedEventsTests : IClassFixture, IDisposable { +public sealed class StreamSubscriptionDeletedEventsTests { readonly StoreFixture _fixture; readonly ILoggerFactory _loggerFactory; readonly LoggingEventListener _listener; - public StreamSubscriptionDeletedEventsTests(StoreFixture fixture, ITestOutputHelper output) { - _fixture = fixture; - _loggerFactory = LoggerFactory.Create(cfg => cfg.AddXunit(output, LogLevel.Debug).SetMinimumLevel(LogLevel.Debug)); + public StreamSubscriptionDeletedEventsTests() { + _fixture = new(); + _loggerFactory = LoggingExtensions.GetLoggerFactory(); _listener = new(_loggerFactory); _fixture.TypeMapper.RegisterKnownEventTypes(typeof(BookingEvents.BookingImported).Assembly); } - [Fact] - [Trait("Category", "Special cases")] - public async Task StreamSubscriptionGetsDeletedEvents() { + [Test] + [Category("Special cases")] + public async Task StreamSubscriptionGetsDeletedEvents(CancellationToken cancellationToken) { var service = new BookingService(_fixture.EventStore); var categoryStream = new StreamName("$ce-Booking"); ulong? startPosition = null; - try { - var last = await _fixture.Client.ReadStreamAsync(Direction.Backwards, categoryStream, StreamPosition.End, 1).ToArrayAsync(); - startPosition = last[0].OriginalEventNumber; - } catch (StreamNotFoundException) { } + // try { + // var last = await _fixture.Client.ReadStreamAsync(Direction.Backwards, categoryStream, StreamPosition.End, 1, cancellationToken: Current.CancellationToken) + // .ToArrayAsync(Current.CancellationToken); + // startPosition = last[0].OriginalEventNumber; + // } catch (StreamNotFoundException) { } const int produceCount = 20; const int deleteCount = 5; var commands = Enumerable.Range(0, produceCount).Select(_ => DomainFixture.CreateImportBooking()).ToArray(); - await Task.WhenAll(commands.Select(x => service.Handle(x, CancellationToken.None))); + foreach (var command in commands) { + await service.Handle(command, CancellationToken.None); + } var delete = Enumerable.Range(5, deleteCount).Select(x => commands[x]).ToList(); @@ -55,19 +59,24 @@ await Task.WhenAll( var subscription = new StreamSubscription( _fixture.Client, new() { - StreamName = categoryStream, - SubscriptionId = subscriptionId, - ResolveLinkTos = true, - ThrowOnError = true, + StreamName = categoryStream, + SubscriptionId = subscriptionId, + ResolveLinkTos = true, + ThrowOnError = true, }, new NoOpCheckpointStore(startPosition), new ConsumePipe().AddSystemEventsFilter().AddDefaultConsumer(handler), eventSerializer: _fixture.Serializer ); - var log = _loggerFactory.CreateLogger("Test"); + var expected = commands.Except(delete).Select(x => x.BookingId); + var log = _loggerFactory.CreateLogger("Test"); + + LogCollection("Produced", commands); + LogCollection("Deleted", delete); + LogCollection("Expected", commands.Except(delete)); - await subscription.SubscribeWithLog(log); + await subscription.SubscribeWithLog(log, cancellationToken); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(200)); @@ -75,10 +84,15 @@ await Task.WhenAll( await Task.Delay(100, cts.Token); } - await subscription.UnsubscribeWithLog(log); + await subscription.UnsubscribeWithLog(log, cancellationToken); var actual = handler.Processed.Select(x => x.Stream.GetId()).ToList(); - actual.Should().BeEquivalentTo(commands.Except(delete).Select(x => x.BookingId)); + log.LogInformation("Actual:\n {Join}", string.Join("\n", actual)); + actual.Should().BeEquivalentTo(expected); + + return; + + void LogCollection(string what, IEnumerable collection) => log.LogInformation("{What}:\n {Join}", what, string.Join("\n", collection.Select(x => x.BookingId))); } class TestHandler : BaseEventHandler { @@ -97,8 +111,22 @@ public override ValueTask HandleEvent(IMessageConsumeContex } } - public void Dispose() { - _loggerFactory.Dispose(); - _listener.Dispose(); + [After(Test)] + public async ValueTask DisposeAsync() { + await _fixture.DisposeAsync(); + await CastAndDispose(_loggerFactory); + await CastAndDispose(_listener); + + return; + + static async ValueTask CastAndDispose(IDisposable resource) { + if (resource is IAsyncDisposable resourceAsyncDisposable) + await resourceAsyncDisposable.DisposeAsync(); + else + resource.Dispose(); + } } + + [Before(Test)] + public Task InitializeAsync() => _fixture.InitializeAsync(); } diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/StreamSubscriptionWithLinksTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/StreamSubscriptionWithLinksTests.cs index eecca8c79..73cad106b 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/StreamSubscriptionWithLinksTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/StreamSubscriptionWithLinksTests.cs @@ -6,37 +6,36 @@ using Eventuous.Tests.Subscriptions.Base; using Microsoft.Extensions.DependencyInjection; using StreamSubscription = Eventuous.EventStore.Subscriptions.StreamSubscription; +// ReSharper disable MethodHasAsyncOverload namespace Eventuous.Tests.EventStore.Subscriptions; -[Collection("Database")] public class StreamSubscriptionWithLinksTests : StoreFixture { const string SubId = "Test"; readonly List _checkpoints = []; readonly string _prefix = $"{Faker.Commerce.ProductAdjective()}{Faker.Commerce.Product()}"; - public StreamSubscriptionWithLinksTests(ITestOutputHelper output) { - Output = output; + public StreamSubscriptionWithLinksTests() { AutoStart = false; TypeMapper.AddType(TestEvent.TypeName); } - [Fact] - [Trait("Category", "Special cases")] + [Test] + [Category("Special cases")] public async Task ShouldHandleAllEventsFromStart() { await Start(); await Execute(1000, null); } - [Fact] - [Trait("Category", "Special cases")] - public async Task ShouldHandleHalfOfTheEvents() { + [Test] + [Category("Special cases")] + public async Task ShouldHandleHalfOfTheEvents(CancellationToken cancellationToken) { const int count = 1000; const int expectedCount = count / 2; var checkpointStore = Provider.GetRequiredService(); - await checkpointStore.StoreCheckpoint(new(SubId, expectedCount - 1), true, default); + await checkpointStore.StoreCheckpoint(new(SubId, expectedCount - 1), true, cancellationToken); await Start(); await Execute(count, expectedCount); @@ -53,7 +52,7 @@ async Task> Seed(IServiceProvider provider, int count) { TypeMap.Instance.AddType(TestEvent.TypeName); var producer = provider.GetRequiredService(); - Output?.WriteLine("Producing events..."); + TestContext.Current?.OutputWriter.WriteLine("Producing events..."); var events = new List(); @@ -64,14 +63,14 @@ async Task> Seed(IServiceProvider provider, int count) { events.Add(evt); } - Output?.WriteLine("Producing complete"); + TestContext.Current?.OutputWriter.WriteLine("Producing complete"); return events; } void ValidateProcessed(IServiceProvider provider, IEnumerable events) { var handler = provider.GetRequiredKeyedService(SubId); - Output?.WriteLine($"Processed {handler.Handled.Count} events"); + TestContext.Current?.OutputWriter.WriteLine($"Processed {handler.Handled.Count} events"); foreach (var evt in events) { handler.Handled.Should().Contain(evt); @@ -100,7 +99,7 @@ async Task WaitForCheckpoint(int count, TimeSpan deadline) { await Task.Delay(500, source.Token); } } catch (OperationCanceledException) { - Output?.WriteLine("Deadline exceeded"); + TestContext.Current?.OutputWriter.WriteLine("Deadline exceeded"); } } @@ -142,7 +141,7 @@ protected override void SetupServices(IServiceCollection services) { return; void CheckpointStoreOnCheckpointStored(object? sender, Checkpoint e) { - Output?.WriteLine($"Stored checkpoint {e.Id}: {e.Position}"); + TestContext.Current?.OutputWriter.WriteLine($"Stored checkpoint {e.Id}: {e.Position}"); _checkpoints.Add(e); } } diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/SubscribeTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/SubscribeTests.cs index 55003f531..5522d779c 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/SubscribeTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/SubscribeTests.cs @@ -7,54 +7,55 @@ namespace Eventuous.Tests.EventStore.Subscriptions; -[Collection("Database")] -public class SubscribeToAll(ITestOutputHelper outputHelper) +public class SubscribeToAll() : SubscribeToAllBase( - outputHelper, - new CatchUpSubscriptionFixture(_ => { }, outputHelper, new("$all"), false) + new CatchUpSubscriptionFixture(_ => { }, new("$all"), false) ) { - [Fact] - public async Task Esdb_ShouldConsumeProducedEvents() { - await ShouldConsumeProducedEvents(); + [Test] + public async Task Esdb_ShouldConsumeProducedEvents(CancellationToken cancellationToken) { + await ShouldConsumeProducedEvents(cancellationToken); } - [Fact] - public async Task Esdb_ShouldConsumeProducedEventsWhenRestarting() { - await ShouldConsumeProducedEventsWhenRestarting(); + [Test] + public async Task Esdb_ShouldConsumeProducedEventsWhenRestarting(CancellationToken cancellationToken) { + await ShouldConsumeProducedEventsWhenRestarting(cancellationToken); } - [Fact] - public async Task Esdb_ShouldUseExistingCheckpoint() { - await ShouldUseExistingCheckpoint(); + [Test] + public async Task Esdb_ShouldUseExistingCheckpoint(CancellationToken cancellationToken) { + await ShouldUseExistingCheckpoint(cancellationToken); } } -[Collection("Database")] -public class SubscribeToStream(ITestOutputHelper outputHelper, StreamNameFixture streamNameFixture) +[ClassDataSource(Shared = SharedType.None)] +public class SubscribeToStream(StreamNameFixture streamNameFixture) : SubscribeToStreamBase( - outputHelper, + streamNameFixture.StreamName, + new CatchUpSubscriptionFixture( + opt => ConfigureOptions(opt, streamNameFixture), streamNameFixture.StreamName, - new CatchUpSubscriptionFixture( - opt => ConfigureOptions(opt, streamNameFixture), - outputHelper, - streamNameFixture.StreamName, - false - ) - ), - IClassFixture { - [Fact] - public async Task Esdb_ShouldConsumeProducedEvents() { - await ShouldConsumeProducedEvents(); + false + ) + ) { + [Before(Test)] + public async Task Setup() => await InitializeAsync(); + + [After(Test)] + public async Task TearDown() => await DisposeAsync(); + + [Test] + public async Task Esdb_ShouldConsumeProducedEvents(CancellationToken cancellationToken) { + await ShouldConsumeProducedEvents(cancellationToken); } - [Fact] - public async Task Esdb_ShouldConsumeProducedEventsWhenRestarting() { - await ShouldConsumeProducedEventsWhenRestarting(); + [Test] + public async Task Esdb_ShouldConsumeProducedEventsWhenRestarting(CancellationToken cancellationToken) { + await ShouldConsumeProducedEventsWhenRestarting(cancellationToken); } - [Fact] - public async Task Esdb_ShouldUseExistingCheckpoint() { - await ShouldUseExistingCheckpoint(); + [Test] + public async Task Esdb_ShouldUseExistingCheckpoint(CancellationToken cancellationToken) { + await ShouldUseExistingCheckpoint(cancellationToken); } static void ConfigureOptions(StreamSubscriptionOptions options, StreamNameFixture streamNameFixture) { diff --git a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/SubscriptionIgnoredMessagesTests.cs b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/SubscriptionIgnoredMessagesTests.cs index 8b3ec79fc..25ca7ad9d 100644 --- a/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/SubscriptionIgnoredMessagesTests.cs +++ b/src/EventStore/test/Eventuous.Tests.EventStore/Subscriptions/SubscriptionIgnoredMessagesTests.cs @@ -1,15 +1,12 @@ -// Copyright (C) Ubiquitous AS.All rights reserved -// Licensed under the Apache License, Version 2.0. - using Eventuous.EventStore.Producers; using Eventuous.EventStore.Subscriptions; using Eventuous.Producers; using Eventuous.Tests.Subscriptions.Base; using Microsoft.Extensions.DependencyInjection; +// ReSharper disable MethodHasAsyncOverload namespace Eventuous.Tests.EventStore.Subscriptions; -[Collection("Database")] public class SubscriptionIgnoredMessagesTests : StoreFixture { readonly string _subscriptionId = $"test-{Guid.NewGuid():N}"; readonly StreamName _stream = new($"test-{Guid.NewGuid():N}"); @@ -17,32 +14,31 @@ public class SubscriptionIgnoredMessagesTests : StoreFixture { ICheckpointStore _checkpointStore = null!; TestEventHandler _handler = null!; - public SubscriptionIgnoredMessagesTests(ITestOutputHelper output) { - Output = output; + public SubscriptionIgnoredMessagesTests() { AutoStart = false; } - [Fact] - [Trait("Category", "Special cases")] - public async Task SubscribeAndProduceManyWithIgnored() { + [Test] + [Category("Special cases")] + public async Task SubscribeAndProduceManyWithIgnored(CancellationToken cancellationToken) { const int count = 10; var testEvents = Generate().ToList(); TypeMapper.AddType(TestEvent.TypeName); TypeMapper.AddType("ignored"); - Output?.WriteLine($"Producing to {_stream}"); - await _producer.Produce(_stream, testEvents, new Metadata()); - Output?.WriteLine("Produce complete"); + TestContext.Current?.OutputWriter.WriteLine($"Producing to {_stream}"); + await _producer.Produce(_stream, testEvents, new Metadata(), cancellationToken: cancellationToken); + TestContext.Current?.OutputWriter.WriteLine("Produce complete"); TypeMapper.RemoveType(); var expected = testEvents.Where(x => x.GetType() == typeof(TestEvent)).ToList(); await Start(); - await _handler.AssertCollection(5.Seconds(), expected).Validate(); + await _handler.AssertCollection(5.Seconds(), expected).Validate(cancellationToken); await DisposeAsync(); - var last = await _checkpointStore.GetLastCheckpoint(_subscriptionId, default); + var last = await _checkpointStore.GetLastCheckpoint(_subscriptionId, cancellationToken); last.Position.Should().Be((ulong)(testEvents.Count - 1)); return; diff --git a/src/Experimental/src/ElasticPlayground/ElasticPlayground.csproj b/src/Experimental/src/ElasticPlayground/ElasticPlayground.csproj index 5a9645724..a68958853 100644 --- a/src/Experimental/src/ElasticPlayground/ElasticPlayground.csproj +++ b/src/Experimental/src/ElasticPlayground/ElasticPlayground.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 Exe false true diff --git a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Eventuous.Extensions.AspNetCore.csproj b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Eventuous.Extensions.AspNetCore.csproj index 2829a5cd7..1d6178dfc 100644 --- a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Eventuous.Extensions.AspNetCore.csproj +++ b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Eventuous.Extensions.AspNetCore.csproj @@ -2,6 +2,7 @@ + diff --git a/src/Extensions/src/Eventuous.Extensions.AspNetCore/Logging/AppBuilderLoggingExtensions.cs b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Logging/AppBuilderLoggingExtensions.cs new file mode 100644 index 000000000..62d8b1930 --- /dev/null +++ b/src/Extensions/src/Eventuous.Extensions.AspNetCore/Logging/AppBuilderLoggingExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (C) Ubiquitous AS.All rights reserved +// Licensed under the Apache License, Version 2.0. + +using System.Diagnostics.Tracing; +using Microsoft.AspNetCore.Builder; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.Hosting; + + +[PublicAPI] +public static class AppBuilderLoggingExtensions { + /// + /// Add Eventuous logging from internal event sources to the application logging + /// + /// Host builder + /// Event level, default is Verbose. Decrease the level to improve performance. + /// Event keywords, default is All + /// + public static IApplicationBuilder UseEventuousLogs(this IApplicationBuilder host, EventLevel level = EventLevel.Verbose, EventKeywords keywords = EventKeywords.All) { + host.ApplicationServices.AddEventuousLogs(level, keywords); + + return host; + } + +} diff --git a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/AggregateFactoryHostExtensions.cs b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/AggregateFactoryHostExtensions.cs deleted file mode 100644 index 48afd218f..000000000 --- a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/AggregateFactoryHostExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) Ubiquitous AS. All rights reserved -// Licensed under the Apache License, Version 2.0. - -// ReSharper disable CheckNamespace - -namespace Microsoft.Extensions.Hosting; - -using DependencyInjection; - -[PublicAPI] -public static class AggregateFactoryBuilderExtensions { - /// - /// Adds registered aggregate factories to the registry. The registry is then used by - /// and - /// - /// - /// - public static IHost UseAggregateFactory(this IHost host) { - UseAggregateFactory(host.Services); - - return host; - } - - static void UseAggregateFactory(IServiceProvider sp) { - var resolvers = sp.GetServices(); - var registry = sp.GetService() ?? AggregateFactoryRegistry.Instance; - - foreach (var resolver in resolvers) { - registry.UnsafeCreateAggregateUsing(resolver.Type, () => resolver.CreateInstance(sp)); - } - } -} diff --git a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Eventuous.Extensions.DependencyInjection.csproj b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Eventuous.Extensions.DependencyInjection.csproj index 448112999..069882f0d 100644 --- a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Eventuous.Extensions.DependencyInjection.csproj +++ b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Eventuous.Extensions.DependencyInjection.csproj @@ -3,11 +3,14 @@ README.md - + + + + diff --git a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/LoggingHostExtensions.cs b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/LoggingHostExtensions.cs deleted file mode 100644 index d1edf03d3..000000000 --- a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/LoggingHostExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) Ubiquitous AS. All rights reserved -// Licensed under the Apache License, Version 2.0. - -using System.Diagnostics.Tracing; -using Eventuous.Diagnostics.Logging; - -// ReSharper disable once CheckNamespace -namespace Microsoft.Extensions.Hosting; - -using DependencyInjection; -using Logging; - -[PublicAPI] -public static class LoggingAppBuilderExtensions { - /// - /// Add Eventuous logging from internal event sources to the application logging - /// - /// Host builder - /// Event level, default is Verbose. Decrease the level to improve performance. - /// Event keywords, default is All - /// - public static IHost UseEventuousLogs(this IHost host, EventLevel level = EventLevel.Verbose, EventKeywords keywords = EventKeywords.All) { - AddEventuousLogs(host.Services, level, keywords); - - return host; - } - - /// - /// Adds the Eventuous logging from internal event sources to the application logging. - /// You'd not normally call this method directly, but use - /// - /// - /// - /// - public static void AddEventuousLogs(this IServiceProvider provider, EventLevel level = EventLevel.Verbose, EventKeywords keywords = EventKeywords.All) { - var factory = provider.GetService(); - - if (factory != null) listener ??= new(factory, level: level, keywords: keywords); - } - - static LoggingEventListener? listener; -} diff --git a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/LoggingServiceProviderExtensions.cs b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/LoggingServiceProviderExtensions.cs new file mode 100644 index 000000000..d155271cb --- /dev/null +++ b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/LoggingServiceProviderExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (C) Ubiquitous AS. All rights reserved +// Licensed under the Apache License, Version 2.0. + +using System.Diagnostics.Tracing; +using Eventuous.Extensions.Logging; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.Hosting; + +using DependencyInjection; +using Logging; + +[PublicAPI] +public static class LoggingServiceProviderExtensions { + /// + /// Adds the Eventuous logging from internal event sources to the application logging. + /// You'd not normally call this method directly, but use UseEventuousLogs from Eventuous.Extensions.AspNetCore/> + /// + /// + /// + /// + public static void AddEventuousLogs(this IServiceProvider provider, EventLevel level = EventLevel.Verbose, EventKeywords keywords = EventKeywords.All) { + var factory = provider.GetService(); + + factory.AddEventuousLogs(level, keywords); + } +} diff --git a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/README.md b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/README.md index 9fa21e40f..cc3a08d9a 100644 --- a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/README.md +++ b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/README.md @@ -3,11 +3,7 @@ This package adds several DI extensions for `IServiceCollection`: - `AddCommandService` to register app services -- `AddAggregateStore` to register the `AggregateStore` and a given `IEventStore` +- `AddAggregateStore` to register the `AggregateStore` and a given `IEventStore` (Eventuous does not need aggregate store to be registered, only use it if you use the aggregate store in your application directly) - `AddAggregate` to register aggregate types that require dependencies Keep in mind that we don't recommend having dependencies in aggregates, so you'd normally not need to use `AddAggregate`. - -When using `AddAggregate`, you should also call `builder.UseAggregateFactory()` in `Startup.Configure`. - -You can also add Eventuous logs to the logging provider by calling `app.AddEventuousLogs()` diff --git a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/AggregateFactory.cs b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/AggregateFactory.cs index 8fe9c9510..f319aca0c 100644 --- a/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/AggregateFactory.cs +++ b/src/Extensions/src/Eventuous.Extensions.DependencyInjection/Registrations/AggregateFactory.cs @@ -43,5 +43,3 @@ public static IServiceCollection AddAggregate(this IServiceCollection return services.AddSingleton(new ResolveAggregateFactory(typeof(T), createInstance)); } } - -record ResolveAggregateFactory(Type Type, Func CreateInstance); diff --git a/src/Extensions/src/Eventuous.Extensions.Logging/Eventuous.Extensions.Logging.csproj b/src/Extensions/src/Eventuous.Extensions.Logging/Eventuous.Extensions.Logging.csproj new file mode 100644 index 000000000..411072b55 --- /dev/null +++ b/src/Extensions/src/Eventuous.Extensions.Logging/Eventuous.Extensions.Logging.csproj @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Extensions/src/Eventuous.Extensions.Logging/LoggerFactoryExtensions.cs b/src/Extensions/src/Eventuous.Extensions.Logging/LoggerFactoryExtensions.cs new file mode 100644 index 000000000..6ea14a56a --- /dev/null +++ b/src/Extensions/src/Eventuous.Extensions.Logging/LoggerFactoryExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (C) Ubiquitous AS. All rights reserved +// Licensed under the Apache License, Version 2.0. + +using System.Diagnostics.Tracing; +using Eventuous.Diagnostics.Logging; +using Microsoft.Extensions.Logging; + +namespace Eventuous.Extensions.Logging; + +public static class LoggerFactoryExtensions { + /// + /// Adds the Eventuous logging from internal event sources to the application logging. + /// Use it only if you are not building an ASP.NET Core application, otherwise use UseEventuousLogs from Eventuous.Extensions.AspNetCore + /// + /// Logger factory instance + /// + /// + public static void AddEventuousLogs(this ILoggerFactory? factory, EventLevel level = EventLevel.Verbose, EventKeywords keywords = EventKeywords.All) { + if (factory != null) listener ??= new(factory, level: level, keywords: keywords); + } + + static LoggingEventListener? listener; +} diff --git a/src/Extensions/test/Eventuous.Sut.AspNetCore/Eventuous.Sut.AspNetCore.csproj b/src/Extensions/test/Eventuous.Sut.AspNetCore/Eventuous.Sut.AspNetCore.csproj index caa2a0c08..35f52bf90 100644 --- a/src/Extensions/test/Eventuous.Sut.AspNetCore/Eventuous.Sut.AspNetCore.csproj +++ b/src/Extensions/test/Eventuous.Sut.AspNetCore/Eventuous.Sut.AspNetCore.csproj @@ -12,7 +12,6 @@ - diff --git a/src/Extensions/test/Eventuous.Tests.DependencyInjection/AggregateFactoryRegistrationTests.cs b/src/Extensions/test/Eventuous.Tests.DependencyInjection/AggregateFactoryRegistrationTests.cs index e8269d35f..89bf1a5eb 100644 --- a/src/Extensions/test/Eventuous.Tests.DependencyInjection/AggregateFactoryRegistrationTests.cs +++ b/src/Extensions/test/Eventuous.Tests.DependencyInjection/AggregateFactoryRegistrationTests.cs @@ -1,7 +1,6 @@ using Eventuous.Tests.AspNetCore.Sut; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; namespace Eventuous.Tests.AspNetCore; @@ -10,12 +9,12 @@ public class AggregateFactoryRegistrationTests { public AggregateFactoryRegistrationTests() { var host = BuildHost(); + host.Services.AddAggregate(); var app = host.Build(); - app.UseAggregateFactory(); _registry = app.Services.GetRequiredService(); } - [Fact] + [Test] public void ShouldCreateNewAggregateWithExplicitFunction() { var instance = _registry.CreateInstance(); instance.Should().BeOfType(); @@ -23,7 +22,7 @@ public void ShouldCreateNewAggregateWithExplicitFunction() { instance.State.Should().NotBeNull(); } - [Fact] + [Test] public void ShouldCreateNewAggregateByResolve() { var instance = _registry.CreateInstance(); instance.Should().BeOfType(); @@ -31,7 +30,7 @@ public void ShouldCreateNewAggregateByResolve() { instance.State.Should().NotBeNull(); } - [Fact] + [Test] public void ShouldCreateTwoSeparateInstances() { var instance1 = _registry.CreateInstance(); var instance2 = _registry.CreateInstance(); diff --git a/src/Extensions/test/Eventuous.Tests.DependencyInjection/Eventuous.Tests.DependencyInjection.csproj b/src/Extensions/test/Eventuous.Tests.DependencyInjection/Eventuous.Tests.DependencyInjection.csproj index c729223a6..0efebea26 100644 --- a/src/Extensions/test/Eventuous.Tests.DependencyInjection/Eventuous.Tests.DependencyInjection.csproj +++ b/src/Extensions/test/Eventuous.Tests.DependencyInjection/Eventuous.Tests.DependencyInjection.csproj @@ -2,6 +2,7 @@ true Eventuous.Tests.AspNetCore + Exe diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitly.verified.txt b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitlyWithoutRouteWithGenericAttr_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt similarity index 100% rename from src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitly.verified.txt rename to src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitlyWithoutRouteWithGenericAttr_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitlyWithoutRoute.verified.txt b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitlyWithoutRoute_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt similarity index 100% rename from src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitlyWithoutRoute.verified.txt rename to src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitlyWithoutRoute_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitlyWithoutRouteWithGenericAttr.verified.txt b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitly_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt similarity index 100% rename from src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitlyWithoutRouteWithGenericAttr.verified.txt rename to src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitly_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitly_tResult=BookingResult.verified.txt b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitly_tResult=BookingResult.verified.txt deleted file mode 100644 index c3dc7761f..000000000 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitly_tResult=BookingResult.verified.txt +++ /dev/null @@ -1,27 +0,0 @@ -{ - state: { - price: { - amount: 100, - currency: EUR - }, - amountPaid: { - amount: 0, - currency: EUR - }, - id: { - value: Guid_1 - } - }, - success: true, - changes: [ - { - event: { - roomId: Guid_2, - price: 100, - checkIn: 2023-10-01, - checkOut: 2023-10-02 - }, - eventType: V1.BookingImported - } - ] -} \ No newline at end of file diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitly_tResult=Result.verified.txt b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitly_tResult=Result.verified.txt deleted file mode 100644 index c3dc7761f..000000000 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapAggregateContractToCommandExplicitly_tResult=Result.verified.txt +++ /dev/null @@ -1,27 +0,0 @@ -{ - state: { - price: { - amount: 100, - currency: EUR - }, - amountPaid: { - amount: 0, - currency: EUR - }, - id: { - value: Guid_1 - } - }, - success: true, - changes: [ - { - event: { - roomId: Guid_2, - price: 100, - checkIn: 2023-10-01, - checkOut: 2023-10-02 - }, - eventType: V1.BookingImported - } - ] -} \ No newline at end of file diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapContractToCommandExplicitly_tResult=BookingResult.verified.txt b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapContractToCommandExplicitly_tResult=BookingResult.verified.txt deleted file mode 100644 index c3dc7761f..000000000 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapContractToCommandExplicitly_tResult=BookingResult.verified.txt +++ /dev/null @@ -1,27 +0,0 @@ -{ - state: { - price: { - amount: 100, - currency: EUR - }, - amountPaid: { - amount: 0, - currency: EUR - }, - id: { - value: Guid_1 - } - }, - success: true, - changes: [ - { - event: { - roomId: Guid_2, - price: 100, - checkIn: 2023-10-01, - checkOut: 2023-10-02 - }, - eventType: V1.BookingImported - } - ] -} \ No newline at end of file diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapContractToCommandExplicitly_tResult=Result.verified.txt b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapContractToCommandExplicitly_tResult=Result.verified.txt deleted file mode 100644 index c3dc7761f..000000000 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapContractToCommandExplicitly_tResult=Result.verified.txt +++ /dev/null @@ -1,27 +0,0 @@ -{ - state: { - price: { - amount: 100, - currency: EUR - }, - amountPaid: { - amount: 0, - currency: EUR - }, - id: { - value: Guid_1 - } - }, - success: true, - changes: [ - { - event: { - roomId: Guid_2, - price: 100, - checkIn: 2023-10-01, - checkOut: 2023-10-02 - }, - eventType: V1.BookingImported - } - ] -} \ No newline at end of file diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapEnrichedCommand.verified.txt b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapEnrichedCommand_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt similarity index 100% rename from src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapEnrichedCommand.verified.txt rename to src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.MapEnrichedCommand_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.cs b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.cs index 82b08d664..1477f4b41 100644 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.cs +++ b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/AggregateCommandsTests.cs @@ -6,11 +6,9 @@ namespace Eventuous.Tests.Extensions.AspNetCore; using static SutBookingCommands; using static Fixture.TestCommands; -public class AggregateCommandsTests(ITestOutputHelper output, WebApplicationFactory factory) - : TestBaseWithLogs(output), IClassFixture> { - readonly ITestOutputHelper _output = output; - - [Fact] +[ClassDataSource>] +public class AggregateCommandsTests(WebApplicationFactory factory) : TestBaseWithLogs() { + [Test] public void RegisterAggregateCommands() { var builder = WebApplication.CreateBuilder(); @@ -21,7 +19,7 @@ public void RegisterAggregateCommands() { b.DataSources.First().Endpoints[0].DisplayName.Should().Be("HTTP: POST book"); } - [Fact] + [Test] public void RegisterAggregatesCommands() { var builder = WebApplication.CreateBuilder(); @@ -32,11 +30,10 @@ public void RegisterAggregatesCommands() { b.DataSources.First().Endpoints[0].DisplayName.Should().Be("HTTP: POST nested-book"); } - [Fact] + [Test] public void MapAggregateContractToCommandExplicitlyWithoutRouteWithWrongGenericAttr() { var act = () => new ServerFixture( factory, - _output, _ => { }, app => app .MapCommands() @@ -46,12 +43,10 @@ public void MapAggregateContractToCommandExplicitlyWithoutRouteWithWrongGenericA act.Should().Throw(); } - - [Fact] + [Test] public async Task MapAggregateContractToCommandExplicitly() { var fixture = new ServerFixture( factory, - _output, _ => { }, app => app .MapCommands() @@ -61,11 +56,10 @@ public async Task MapAggregateContractToCommandExplicitly() { await Execute(fixture, ImportRoute); } - [Fact] + [Test] public async Task MapAggregateContractToCommandExplicitlyWithoutRoute() { var fixture = new ServerFixture( factory, - _output, _ => { }, app => app .MapCommands() @@ -75,11 +69,10 @@ public async Task MapAggregateContractToCommandExplicitlyWithoutRoute() { await Execute(fixture, Import1Route); } - [Fact] + [Test] public async Task MapAggregateContractToCommandExplicitlyWithoutRouteWithGenericAttr() { var fixture = new ServerFixture( factory, - _output, _ => { }, app => app .MapCommands() @@ -89,17 +82,16 @@ public async Task MapAggregateContractToCommandExplicitlyWithoutRouteWithGeneric await Execute(fixture, Import2Route); } - [Fact] + [Test] public async Task MapEnrichedCommand() { var fixture = new ServerFixture( factory, - _output, _ => { }, app => app .MapCommands() .MapCommand((x, _) => x with { GuestId = TestData.GuestId }) ); - var cmd = fixture.GetBookRoom(); + var cmd = fixture.GetBookRoom(); var content = await fixture.ExecuteRequest(cmd, "book", cmd.BookingId); await VerifyJson(content); } diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/ControllerTests.cs b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/ControllerTests.cs index 1c533a45c..d22bee757 100644 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/ControllerTests.cs +++ b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/ControllerTests.cs @@ -1,18 +1,19 @@ +using Eventuous.TestHelpers.TUnit; using Microsoft.AspNetCore.Mvc.Testing; using static Eventuous.Sut.App.Commands; using static Eventuous.Sut.AspNetCore.BookingApi; namespace Eventuous.Tests.Extensions.AspNetCore; -using TestHelpers; using Fixture; using static SutBookingCommands; -public class ControllerTests : IDisposable, IClassFixture> { +[ClassDataSource>] +public class ControllerTests : IDisposable { readonly ServerFixture _fixture; readonly TestEventListener _listener; - public ControllerTests(WebApplicationFactory factory, ITestOutputHelper output) { + public ControllerTests(WebApplicationFactory factory) { var commandMap = new CommandMap() .Add( (x, ctx) => new(new(x.BookingId), x.PaymentId, new(x.Amount), x.PaidAt) { PaidBy = ctx.User.Identity?.Name } @@ -20,7 +21,6 @@ public ControllerTests(WebApplicationFactory factory, ITestOutputHelper _fixture = new( factory, - output, services => { services.AddSingleton(commandMap); services.AddControllers(); @@ -31,21 +31,21 @@ public ControllerTests(WebApplicationFactory factory, ITestOutputHelper } ); - _listener = new(output); + _listener = new(); } - [Fact] - public async Task RecordPaymentUsingMappedCommand() { + [Test] + public async Task RecordPaymentUsingMappedCommand(CancellationToken cancellationToken) { using var client = _fixture.GetClient(); var bookRoom = _fixture.GetBookRoom(); - await client.PostJsonAsync("/book", bookRoom); + await client.PostJsonAsync("/book", bookRoom, cancellationToken: cancellationToken); var registerPayment = new RegisterPaymentHttp(bookRoom.BookingId, bookRoom.RoomId, 100, DateTimeOffset.Now); var request = new RestRequest("/v2/pay").AddJsonBody(registerPayment); - var response = await client.ExecutePostAsync.Ok>(request); + var response = await client.ExecutePostAsync.Ok>(request, cancellationToken: cancellationToken); response.StatusCode.Should().Be(HttpStatusCode.OK); var expected = new BookingEvents.BookingFullyPaid(registerPayment.PaidAt); diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/DiscoveredCommandsTests.CallDiscoveredCommandRoute.verified.txt b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/DiscoveredCommandsTests.CallDiscoveredCommandRoute_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt similarity index 100% rename from src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/DiscoveredCommandsTests.CallDiscoveredCommandRoute.verified.txt rename to src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/DiscoveredCommandsTests.CallDiscoveredCommandRoute_factory=Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1[Program].verified.txt diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/DiscoveredCommandsTests.cs b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/DiscoveredCommandsTests.cs index 442bb9b0c..05af186aa 100644 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/DiscoveredCommandsTests.cs +++ b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/DiscoveredCommandsTests.cs @@ -6,15 +6,12 @@ namespace Eventuous.Tests.Extensions.AspNetCore; using static SutBookingCommands; using Fixture; -public class DiscoveredCommandsTests(ITestOutputHelper output, WebApplicationFactory factory) - : TestBaseWithLogs(output), IClassFixture> { - readonly ITestOutputHelper _output = output; - - [Fact] +[ClassDataSource>] +public class DiscoveredCommandsTests(WebApplicationFactory factory) : TestBaseWithLogs() { + [Test] public async Task CallDiscoveredCommandRoute() { var fixture = new ServerFixture( factory, - _output, _ => { }, app => app.MapDiscoveredCommands(typeof(NestedCommands).Assembly) ); diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Eventuous.Tests.Extensions.AspNetCore.csproj b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Eventuous.Tests.Extensions.AspNetCore.csproj index 9446e2267..140f675d7 100644 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Eventuous.Tests.Extensions.AspNetCore.csproj +++ b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Eventuous.Tests.Extensions.AspNetCore.csproj @@ -2,9 +2,11 @@ true true + Exe - + + @@ -12,8 +14,7 @@ - - + diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Fixture/ServerFixture.cs b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Fixture/ServerFixture.cs index b033d7355..13ac44e5e 100644 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Fixture/ServerFixture.cs +++ b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Fixture/ServerFixture.cs @@ -3,6 +3,7 @@ using System.Text.Json; using Eventuous.TestHelpers; +using Eventuous.TestHelpers.TUnit.Logging; using Microsoft.AspNetCore.Mvc.Testing; using RestSharp.Serializers.Json; @@ -15,7 +16,6 @@ public class ServerFixture { public ServerFixture( WebApplicationFactory factory, - ITestOutputHelper output, Action? register = null, ConfigureWebApplication? configure = null ) { @@ -29,7 +29,7 @@ public ServerFixture( if (configure != null) services.AddSingleton(configure); } ) - .ConfigureLogging(x => x.AddXunit(output).AddConsole().SetMinimumLevel(LogLevel.Debug)); + .ConfigureLogging(x => x.ForTests()); } ); builder.Server.PreserveExecutionContext = false; @@ -41,7 +41,7 @@ public ServerFixture( readonly WebApplicationFactory _app; public RestClient GetClient() { - return new RestClient( + return new( _app.CreateClient(), disposeHttpClient: true, configureSerialization: s => s.UseSerializer(() => new SystemTextJsonSerializer(_options)) diff --git a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Fixture/TestBaseWithLogs.cs b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Fixture/TestBaseWithLogs.cs index 76dd212e4..d42901083 100644 --- a/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Fixture/TestBaseWithLogs.cs +++ b/src/Extensions/test/Eventuous.Tests.Extensions.AspNetCore/Fixture/TestBaseWithLogs.cs @@ -1,9 +1,9 @@ -using Eventuous.TestHelpers; +using Eventuous.TestHelpers.TUnit; namespace Eventuous.Tests.Extensions.AspNetCore.Fixture; -public abstract class TestBaseWithLogs(ITestOutputHelper output) : IDisposable { - readonly TestEventListener _listener = new(output); +public abstract class TestBaseWithLogs : IDisposable { + readonly TestEventListener _listener = new(); public void Dispose() => _listener.Dispose(); } diff --git a/src/Gateway/test/Eventuous.Tests.Gateway/Eventuous.Tests.Gateway.csproj b/src/Gateway/test/Eventuous.Tests.Gateway/Eventuous.Tests.Gateway.csproj index 80470d47e..39e128d09 100644 --- a/src/Gateway/test/Eventuous.Tests.Gateway/Eventuous.Tests.Gateway.csproj +++ b/src/Gateway/test/Eventuous.Tests.Gateway/Eventuous.Tests.Gateway.csproj @@ -1,6 +1,7 @@ true + Exe diff --git a/src/Gateway/test/Eventuous.Tests.Gateway/RegistrationTests.cs b/src/Gateway/test/Eventuous.Tests.Gateway/RegistrationTests.cs index 2987b5f93..0fdd69cbf 100644 --- a/src/Gateway/test/Eventuous.Tests.Gateway/RegistrationTests.cs +++ b/src/Gateway/test/Eventuous.Tests.Gateway/RegistrationTests.cs @@ -13,11 +13,12 @@ namespace Eventuous.Tests.Gateway; -public class RegistrationTests : IDisposable { - readonly TestServer _host = new(BuildHost()); - - [Fact] - public void Test() { } +public class RegistrationTests { + [Test] + public void Test() { + TestServer host = new(BuildHost()); + host.Dispose(); + } static IWebHostBuilder BuildHost() => new WebHostBuilder().UseStartup(); @@ -50,11 +51,11 @@ class TestProducer : BaseProducer { public List ProducedMessages { get; } = []; protected override Task ProduceMessages( - StreamName stream, - IEnumerable messages, - TestProduceOptions? options, - CancellationToken cancellationToken = default - ) { + StreamName stream, + IEnumerable messages, + TestProduceOptions? options, + CancellationToken cancellationToken = default + ) { ProducedMessages.AddRange(messages); return Task.CompletedTask; @@ -62,6 +63,4 @@ protected override Task ProduceMessages( } record TestProduceOptions; - - public void Dispose() => _host.Dispose(); } diff --git a/src/GooglePubSub/src/Eventuous.GooglePubSub/Producers/GooglePubSubProducer.cs b/src/GooglePubSub/src/Eventuous.GooglePubSub/Producers/GooglePubSubProducer.cs index 715c6edc8..270ee0a12 100644 --- a/src/GooglePubSub/src/Eventuous.GooglePubSub/Producers/GooglePubSubProducer.cs +++ b/src/GooglePubSub/src/Eventuous.GooglePubSub/Producers/GooglePubSubProducer.cs @@ -39,12 +39,11 @@ public GooglePubSubProducer( /// Producer options /// Optional event serializer. Will use the default instance if missing. /// Optional logger instance - public GooglePubSubProducer(PubSubProducerOptions options, IEventSerializer? serializer = null, ILogger? log = null) - : base(TracingOptions) { + public GooglePubSubProducer(PubSubProducerOptions options, IEventSerializer? serializer = null, ILogger? log = null) : base(TracingOptions) { Ensure.NotNull(options); _serializer = serializer ?? DefaultEventSerializer.Instance; - _clientCache = new ClientCache(options, log); + _clientCache = new(options, log); _attributes = options.Attributes; _log = log; } diff --git a/src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/Eventuous.Tests.GooglePubSub.csproj b/src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/Eventuous.Tests.GooglePubSub.csproj index 0dd870442..34de9ee70 100644 --- a/src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/Eventuous.Tests.GooglePubSub.csproj +++ b/src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/Eventuous.Tests.GooglePubSub.csproj @@ -1,4 +1,7 @@ + + Exe + diff --git a/src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/PubSubFixture.cs b/src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/PubSubFixture.cs index 9d7518f89..5d596c151 100644 --- a/src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/PubSubFixture.cs +++ b/src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/PubSubFixture.cs @@ -1,22 +1,23 @@ using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; using Google.Api.Gax; +using TUnit.Core.Interfaces; namespace Eventuous.Tests.GooglePubSub; -public class PubSubFixture : IAsyncLifetime { +public class PubSubFixture : IAsyncInitializer, IAsyncDisposable { public static string PubsubProjectId => "test-id"; - public static async Task DeleteSubscription(string subscriptionId) { + public static async Task DeleteSubscription(string subscriptionId, CancellationToken cancellationToken) { var builder = new SubscriberServiceApiClientBuilder { EmulatorDetection = EmulatorDetection.EmulatorOnly }; - var subscriber = await builder.BuildAsync(); + var subscriber = await builder.BuildAsync(cancellationToken); var subscriptionName = SubscriptionName.FromProjectSubscription(PubsubProjectId, subscriptionId); await subscriber.DeleteSubscriptionAsync(subscriptionName); } - public static async Task DeleteTopic(string topicId) { + public static async Task DeleteTopic(string topicId, CancellationToken cancellationToken) { var builder = new PublisherServiceApiClientBuilder { EmulatorDetection = EmulatorDetection.EmulatorOnly }; - var publisher = await builder.BuildAsync(); + var publisher = await builder.BuildAsync(cancellationToken); var topicName = TopicName.FromProjectTopic(PubsubProjectId, topicId); await publisher.DeleteTopicAsync(topicName); } @@ -38,7 +39,7 @@ public async Task InitializeAsync() { Environment.SetEnvironmentVariable("PUBSUB_PROJECT_ID", PubsubProjectId); } - public async Task DisposeAsync() { + public async ValueTask DisposeAsync() { await _container.StopAsync(); } } diff --git a/src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/PubSubTests.cs b/src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/PubSubTests.cs index da8c1ec4d..988c7a28a 100644 --- a/src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/PubSubTests.cs +++ b/src/GooglePubSub/test/Eventuous.Tests.GooglePubSub/PubSubTests.cs @@ -2,12 +2,14 @@ using Eventuous.GooglePubSub.Subscriptions; using Eventuous.Producers; using Eventuous.Subscriptions.Filters; +using Eventuous.TestHelpers.TUnit.Logging; using Eventuous.Tests.Subscriptions.Base; using Google.Api.Gax; namespace Eventuous.Tests.GooglePubSub; -public class PubSubTests : IAsyncLifetime, IClassFixture { +[ClassDataSource(Shared = SharedType.ForClass)] +public class PubSubTests { static PubSubTests() => TypeMap.Instance.RegisterKnownEventTypes(typeof(TestEvent).Assembly); static readonly Fixture Auto = new(); @@ -20,8 +22,8 @@ public class PubSubTests : IAsyncLifetime, IClassFixture { readonly ILogger _log; // ReSharper disable once UnusedParameter.Local - public PubSubTests(PubSubFixture _, ITestOutputHelper outputHelper) { - var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug).AddXunit(outputHelper)); + public PubSubTests(PubSubFixture _) { + var loggerFactory = LoggingExtensions.GetLoggerFactory(); _log = loggerFactory.CreateLogger(); _pubsubTopic = new($"test-{Guid.NewGuid():N}"); @@ -45,35 +47,39 @@ public PubSubTests(PubSubFixture _, ITestOutputHelper outputHelper) { ); } - [Fact] - public async Task SubscribeAndProduce() { + [Test] + [Retry(3)] + public async Task SubscribeAndProduce(CancellationToken cancellationToken) { var testEvent = Auto.Create(); - await _producer.Produce(_pubsubTopic, testEvent, null); + await _producer.Produce(_pubsubTopic, testEvent, null, cancellationToken: cancellationToken); - await _handler.AssertThat().Timebox(10.Seconds()).Any().Match(x => x as TestEvent == testEvent).Validate(); + await _handler.AssertThat().Timebox(10.Seconds()).Any().Match(x => x as TestEvent == testEvent).Validate(cancellationToken); } - [Fact] - public async Task SubscribeAndProduceMany() { + [Test] + [Retry(3)] + public async Task SubscribeAndProduceMany(CancellationToken cancellationToken) { const int count = 10000; var testEvents = Auto.CreateMany(count).ToList(); - await _producer.Produce(_pubsubTopic, testEvents, null); - await _handler.AssertCollection(10.Seconds(), [..testEvents]).Validate(); + await _producer.Produce(_pubsubTopic, testEvents, null, cancellationToken: cancellationToken); + await _handler.AssertCollection(40.Seconds(), [..testEvents]).Validate(cancellationToken); } - public async Task InitializeAsync() { - await _producer.StartAsync(); - await _subscription.SubscribeWithLog(_log); + [Before(Test)] + public async Task InitializeAsync(CancellationToken cancellationToken) { + await _producer.StartAsync(cancellationToken); + await _subscription.SubscribeWithLog(_log, cancellationToken); } - public async Task DisposeAsync() { - await _producer.StopAsync(); - await _subscription.UnsubscribeWithLog(_log); + [After(Test)] + public async Task DisposeAsync(CancellationToken cancellationToken) { + await _producer.StopAsync(cancellationToken); + await _subscription.UnsubscribeWithLog(_log, cancellationToken); - await PubSubFixture.DeleteSubscription(_pubsubSubscription); - await PubSubFixture.DeleteTopic(_pubsubTopic); + await PubSubFixture.DeleteSubscription(_pubsubSubscription, cancellationToken); + await PubSubFixture.DeleteTopic(_pubsubTopic, cancellationToken); } } diff --git a/src/Kafka/test/Eventuous.Tests.Kafka/BasicProducerTests.cs b/src/Kafka/test/Eventuous.Tests.Kafka/BasicProducerTests.cs index dfdca9c52..4c2fcabef 100644 --- a/src/Kafka/test/Eventuous.Tests.Kafka/BasicProducerTests.cs +++ b/src/Kafka/test/Eventuous.Tests.Kafka/BasicProducerTests.cs @@ -7,24 +7,25 @@ using static System.String; using static Eventuous.DeserializationResult; +// ReSharper disable MethodHasAsyncOverload + namespace Eventuous.Tests.Kafka; -public class BasicProducerTests : IClassFixture { - readonly KafkaFixture _fixture; - readonly ITestOutputHelper _output; +[ClassDataSource] +public class BasicProducerTests { + readonly KafkaFixture _fixture; - public BasicProducerTests(KafkaFixture fixture, ITestOutputHelper output) { + public BasicProducerTests(KafkaFixture fixture) { _fixture = fixture; - _output = output; TypeMap.Instance.AddType("testEvent"); } static readonly Fixture Auto = new(); - [Fact] - public async Task ShouldProduceAndWait() { + [Test] + public async Task ShouldProduceAndWait(CancellationToken cancellationToken) { var topicName = Auto.Create(); - _output.WriteLine($"Topic: {topicName}"); + TestContext.Current?.OutputWriter.WriteLine($"Topic: {topicName}"); var events = Auto.CreateMany().ToArray(); @@ -32,15 +33,15 @@ public async Task ShouldProduceAndWait() { var consumed = new List(); await ExecuteConsume().NoThrow(); - _output.WriteLine($"Consumed {consumed.Count} events"); + TestContext.Current?.OutputWriter.WriteLine($"Consumed {consumed.Count} events"); consumed.Should().BeEquivalentTo(events); return; async Task Produce() { await using var producer = new KafkaBasicProducer(new(new() { BootstrapServers = _fixture.BootstrapServers })); - await producer.StartAsync(default); - await producer.Produce(new(topicName), events, new(), new("test")); + await producer.StartAsync(cancellationToken); + await producer.Produce(new(topicName), events, new(), new("test"), cancellationToken: cancellationToken); } async Task ExecuteConsume() { @@ -55,11 +56,11 @@ async Task ExecuteConsume() { } } - async Task Consume(IConsumer c, CancellationToken cancellationToken) { - var msg = c.Consume(cancellationToken); + async Task Consume(IConsumer c, CancellationToken ct) { + var msg = c.Consume(ct); if (msg == null) { - await Task.Delay(100, cancellationToken); + await Task.Delay(100, ct); return; } @@ -72,7 +73,7 @@ async Task Consume(IConsumer c, CancellationToken cancellationTo var result = DefaultEventSerializer.Instance.DeserializeEvent(msg.Message.Value, messageType!, contentType!) as SuccessfullyDeserialized; var evt = (result!.Payload as TestEvent)!; - _output.WriteLine($"Consumed {evt}"); + TestContext.Current?.OutputWriter.WriteLine($"Consumed {evt}"); consumed.Add(evt); } } @@ -90,11 +91,11 @@ IConsumer GetConsumer(string groupId) { }; return new ConsumerBuilder(config) - .SetErrorHandler((_, e) => _output.WriteLine($"Error: {e.Reason}")) - .SetStatisticsHandler((_, json) => _output.WriteLine($"Statistics: {json}")) + .SetErrorHandler((_, e) => TestContext.Current?.OutputWriter.WriteLine($"Error: {e.Reason}")) + .SetStatisticsHandler((_, json) => TestContext.Current?.OutputWriter.WriteLine($"Statistics: {json}")) .SetPartitionsAssignedHandler( (c, partitions) => { - _output.WriteLine( + TestContext.Current?.OutputWriter.WriteLine( $"Partitions incrementally assigned: [{Join(',', partitions.Select(p => p.Partition.Value))}], all: [{Join(',', c.Assignment.Concat(partitions).Select(p => p.Partition.Value))}]" ); } @@ -103,12 +104,12 @@ IConsumer GetConsumer(string groupId) { (c, partitions) => { var remaining = c.Assignment.Where(atp => partitions.All(rtp => rtp.TopicPartition != atp)); - _output.WriteLine( + TestContext.Current?.OutputWriter.WriteLine( $"Partitions incrementally revoked: [{Join(',', partitions.Select(p => p.Partition.Value))}], remaining: [{Join(',', remaining.Select(p => p.Partition.Value))}]" ); } ) - .SetPartitionsLostHandler((_, partitions) => _output.WriteLine($"Partitions were lost: [{Join(", ", partitions)}]")) + .SetPartitionsLostHandler((_, partitions) => TestContext.Current?.OutputWriter.WriteLine($"Partitions were lost: [{Join(", ", partitions)}]")) .Build(); } } diff --git a/src/Kafka/test/Eventuous.Tests.Kafka/Eventuous.Tests.Kafka.csproj b/src/Kafka/test/Eventuous.Tests.Kafka/Eventuous.Tests.Kafka.csproj index 10ca12d94..31a7ce2e0 100644 --- a/src/Kafka/test/Eventuous.Tests.Kafka/Eventuous.Tests.Kafka.csproj +++ b/src/Kafka/test/Eventuous.Tests.Kafka/Eventuous.Tests.Kafka.csproj @@ -1,8 +1,9 @@ false - osx.13-arm64 + osx-arm64 false + Exe diff --git a/src/Kafka/test/Eventuous.Tests.Kafka/KafkaFixture.cs b/src/Kafka/test/Eventuous.Tests.Kafka/KafkaFixture.cs index 714ba2c3c..a430c22f8 100644 --- a/src/Kafka/test/Eventuous.Tests.Kafka/KafkaFixture.cs +++ b/src/Kafka/test/Eventuous.Tests.Kafka/KafkaFixture.cs @@ -1,11 +1,9 @@ -// Copyright (C) Ubiquitous AS.All rights reserved -// Licensed under the Apache License, Version 2.0. - using Testcontainers.Kafka; +using TUnit.Core.Interfaces; namespace Eventuous.Tests.Kafka; -public class KafkaFixture : IAsyncLifetime { +public class KafkaFixture : IAsyncInitializer, IAsyncDisposable { KafkaContainer _kafkaContainer = null!; public async Task InitializeAsync() { @@ -17,7 +15,5 @@ public async Task InitializeAsync() { public string BootstrapServers => _kafkaContainer.GetBootstrapAddress(); - public async Task DisposeAsync() { - await _kafkaContainer.DisposeAsync(); - } + public async ValueTask DisposeAsync() => await _kafkaContainer.DisposeAsync(); } diff --git a/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/Eventuous.Tests.Projections.MongoDB.csproj b/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/Eventuous.Tests.Projections.MongoDB.csproj index a93ce3baa..406194be9 100644 --- a/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/Eventuous.Tests.Projections.MongoDB.csproj +++ b/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/Eventuous.Tests.Projections.MongoDB.csproj @@ -1,17 +1,20 @@ - - true - true - true - - - - - - - - - - - + + true + true + true + Exe + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/Fixtures/IntegrationFixture.cs b/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/Fixtures/IntegrationFixture.cs index 2cc041283..8f3d66416 100644 --- a/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/Fixtures/IntegrationFixture.cs +++ b/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/Fixtures/IntegrationFixture.cs @@ -5,10 +5,11 @@ using MongoDB.Driver; using Testcontainers.EventStoreDb; using Testcontainers.MongoDb; +using TUnit.Core.Interfaces; namespace Eventuous.Tests.Projections.MongoDB.Fixtures; -public sealed class IntegrationFixture : IAsyncLifetime { +public sealed class IntegrationFixture : IAsyncInitializer, IAsyncDisposable { public IEventStore EventStore { get; set; } = null!; public EventStoreClient Client { get; private set; } = null!; public IMongoDatabase Mongo { get; private set; } = null!; @@ -44,7 +45,7 @@ public async Task InitializeAsync() { Mongo = new MongoClient(mongoSettings).GetDatabase("bookings"); } - public async Task DisposeAsync() { + public async ValueTask DisposeAsync() { await Client.DisposeAsync(); await _esdbContainer.DisposeAsync(); } diff --git a/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBuilder.cs b/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBuilder.cs index b376c06b6..ef8222241 100644 --- a/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBuilder.cs +++ b/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBuilder.cs @@ -7,9 +7,9 @@ namespace Eventuous.Tests.Projections.MongoDB; -public class ProjectWithBuilder(IntegrationFixture fixture, ITestOutputHelper output) - : ProjectionTestBase(nameof(ProjectWithBuilder), fixture, output) { - [Fact] +[ClassDataSource] +public class ProjectWithBuilder(IntegrationFixture fixture) : ProjectionTestBase(nameof(ProjectWithBuilder), fixture) { + [Test] public async Task ShouldProjectImported() { var evt = DomainFixture.CreateImportBooking(); var id = new BookingId(CreateId()); diff --git a/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBulkBuilder.cs b/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBulkBuilder.cs index 5010d41fb..2b60a752f 100644 --- a/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBulkBuilder.cs +++ b/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBulkBuilder.cs @@ -7,9 +7,9 @@ namespace Eventuous.Tests.Projections.MongoDB; -public class ProjectWithBulkBuilder(IntegrationFixture fixture, ITestOutputHelper output) - : ProjectionTestBase(nameof(ProjectWithBulkBuilder), fixture, output) { - [Fact] +[ClassDataSource] +public class ProjectWithBulkBuilder(IntegrationFixture fixture) : ProjectionTestBase(nameof(ProjectWithBulkBuilder), fixture) { + [Test] public async Task ShouldProjectImported() { var evt = DomainFixture.CreateImportBooking(); var id = new BookingId(CreateId()); @@ -60,7 +60,7 @@ public SutBulkProjection(IMongoDatabase database) .AddOperation( x => x.InsertOne .Document( - ctx => new BookingDocument(ctx.Stream.GetId()) { + ctx => new(ctx.Stream.GetId()) { RoomId = ctx.Message.RoomId, CheckInDate = ctx.Message.CheckIn, CheckOutDate = ctx.Message.CheckOut, @@ -77,7 +77,7 @@ public SutBulkProjection(IMongoDatabase database) .AddOperation( x => x.InsertOne .Document( - ctx => new BookingDocument(ctx.Stream.GetId()) { + ctx => new(ctx.Stream.GetId()) { BookingPrice = ctx.Message.Price, Outstanding = ctx.Message.Price } diff --git a/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectingWithTypedHandlers.cs b/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectingWithTypedHandlers.cs index 304f8ee4d..fdedff881 100644 --- a/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectingWithTypedHandlers.cs +++ b/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectingWithTypedHandlers.cs @@ -7,10 +7,11 @@ namespace Eventuous.Tests.Projections.MongoDB; -public sealed class ProjectingWithTypedHandlers(IntegrationFixture fixture, ITestOutputHelper output) - : ProjectionTestBase(nameof(ProjectingWithTypedHandlers), fixture, output) { - [Fact] - public async Task ShouldProjectImported() { +[ClassDataSource] +public sealed class ProjectingWithTypedHandlers(IntegrationFixture fixture) + : ProjectionTestBase(nameof(ProjectingWithTypedHandlers), fixture) { + [Test] + public async Task ShouldProjectImported(CancellationToken cancellationToken) { var evt = DomainFixture.CreateImportBooking(); var id = new BookingId(CreateId()); var stream = StreamNameFactory.For(id); @@ -29,7 +30,7 @@ public async Task ShouldProjectImported() { StreamPosition = (ulong)append.NextExpectedVersion }; - var actual = await Fixture.Mongo.LoadDocument(id.ToString()); + var actual = await Fixture.Mongo.LoadDocument(id.ToString(), cancellationToken: cancellationToken); actual.Should().Be(expected); } diff --git a/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectionTestBase.cs b/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectionTestBase.cs index 219d2dac3..781624644 100644 --- a/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectionTestBase.cs +++ b/src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectionTestBase.cs @@ -2,28 +2,42 @@ using Eventuous.Projections.MongoDB; using Eventuous.Subscriptions; using Eventuous.Subscriptions.Checkpoints; +using Eventuous.TestHelpers.TUnit.Logging; using Eventuous.Tests.Projections.MongoDB.Fixtures; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace Eventuous.Tests.Projections.MongoDB; -public class ProjectionTestBase : IClassFixture, IAsyncLifetime where TProjection : class, IEventHandler { - protected readonly IntegrationFixture Fixture; - protected readonly IHost Host; +public abstract class ProjectionTestBase { + readonly string _id; + protected IHost Host = null!; + readonly IHostBuilder _builder; - protected ProjectionTestBase(string id, IntegrationFixture fixture, ITestOutputHelper output) { - Fixture = fixture; + protected ProjectionTestBase(string id) { + _id = id; + _builder = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder().ConfigureLogging(cfg => cfg.ForTests()); + } - var builder = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder() - .ConfigureLogging(cfg => cfg.AddXunit(output, LogLevel.Debug).SetMinimumLevel(LogLevel.Trace)) - .ConfigureServices(collection => ConfigureServices(collection, id)); + protected abstract void ConfigureServices(IServiceCollection services, string id); - Host = builder.Build(); - Host.UseEventuousLogs(); + [Before(Test)] + public async Task InitializeAsync() { + _builder.ConfigureServices(collection => ConfigureServices(collection, _id)); + Host = _builder.Build(); + Host.Services.AddEventuousLogs(); + await Host.StartAsync(); } - void ConfigureServices(IServiceCollection services, string id) + [After(Test)] + public async Task DisposeAsync() => await Host.StopAsync(); +} + +public abstract class ProjectionTestBase(string id, IntegrationFixture fixture) : ProjectionTestBase(id) + where TProjection : class, IEventHandler { + protected readonly IntegrationFixture Fixture = fixture; + + protected override void ConfigureServices(IServiceCollection services, string id) => services .AddSingleton(Fixture.Client) .AddSingleton(Fixture.Mongo) @@ -37,7 +51,7 @@ void ConfigureServices(IServiceCollection services, string id) protected async Task WaitForPosition(ulong position) { var checkpointStore = Host.Services.GetRequiredService(); - var count = 100; + var count = 100; while (count-- > 0) { var checkpoint = await checkpointStore.GetLastCheckpoint(nameof(ProjectWithBuilder), default); @@ -47,10 +61,4 @@ protected async Task WaitForPosition(ulong position) { await Task.Delay(100); } } - - public Task InitializeAsync() - => Host.StartAsync(); - - public Task DisposeAsync() - => Host.StopAsync(); } diff --git a/src/Postgres/src/Eventuous.Postgresql/Schema.cs b/src/Postgres/src/Eventuous.Postgresql/Schema.cs index c71611435..72df29800 100644 --- a/src/Postgres/src/Eventuous.Postgresql/Schema.cs +++ b/src/Postgres/src/Eventuous.Postgresql/Schema.cs @@ -13,7 +13,7 @@ namespace Eventuous.Postgresql; public class Schema(string schema = Schema.DefaultSchema) { public const string DefaultSchema = "eventuous"; - public static string GetStreamMessageTypeName(string schema = Schema.DefaultSchema) => $"{schema}.stream_message"; + public static string GetStreamMessageTypeName(string schema = DefaultSchema) => $"{schema}.stream_message"; public string StreamMessage => GetStreamMessageTypeName(schema); public string AppendEvents => $"select * from {schema}.append_events(@_stream_name, @_expected_version, @_created, @_messages)"; diff --git a/src/Postgres/test/Eventuous.Tests.Postgres/Eventuous.Tests.Postgres.csproj b/src/Postgres/test/Eventuous.Tests.Postgres/Eventuous.Tests.Postgres.csproj index 5ca5eb77c..84c31fafe 100644 --- a/src/Postgres/test/Eventuous.Tests.Postgres/Eventuous.Tests.Postgres.csproj +++ b/src/Postgres/test/Eventuous.Tests.Postgres/Eventuous.Tests.Postgres.csproj @@ -3,12 +3,14 @@ true true true + Exe + diff --git a/src/Postgres/test/Eventuous.Tests.Postgres/Metrics/MetricsFixture.cs b/src/Postgres/test/Eventuous.Tests.Postgres/Metrics/MetricsFixture.cs index 912e2c5d4..7f8e426b2 100644 --- a/src/Postgres/test/Eventuous.Tests.Postgres/Metrics/MetricsFixture.cs +++ b/src/Postgres/test/Eventuous.Tests.Postgres/Metrics/MetricsFixture.cs @@ -8,8 +8,7 @@ namespace Eventuous.Tests.Postgres.Metrics; -public class MetricsFixture - : MetricsSubscriptionFixtureBase { +public class MetricsFixture : MetricsSubscriptionFixtureBase { readonly string _schemaName = GetSchemaName(); protected override PostgreSqlContainer CreateContainer() => PostgresContainer.Create(); diff --git a/src/Postgres/test/Eventuous.Tests.Postgres/Metrics/MetricsTests.cs b/src/Postgres/test/Eventuous.Tests.Postgres/Metrics/MetricsTests.cs index c7f5179ba..b7553cf13 100644 --- a/src/Postgres/test/Eventuous.Tests.Postgres/Metrics/MetricsTests.cs +++ b/src/Postgres/test/Eventuous.Tests.Postgres/Metrics/MetricsTests.cs @@ -1,12 +1,10 @@ -using Eventuous.Postgresql.Subscriptions; -using Eventuous.Sql.Base.Producers; using Eventuous.Tests.OpenTelemetry; -using Testcontainers.PostgreSql; // ReSharper disable UnusedType.Global namespace Eventuous.Tests.Postgres.Metrics; -[Collection("Database")] -public class MetricsTests(ITestOutputHelper outputHelper) - : MetricsTestsBase(outputHelper); +[ClassDataSource] +[InheritsTests] +public class MetricsTests(MetricsFixture fixture) : MetricsTestsBase(fixture); + diff --git a/src/Postgres/test/Eventuous.Tests.Postgres/Projections/ProjectorTests.cs b/src/Postgres/test/Eventuous.Tests.Postgres/Projections/ProjectorTests.cs index e8f02e744..b01c61df1 100644 --- a/src/Postgres/test/Eventuous.Tests.Postgres/Projections/ProjectorTests.cs +++ b/src/Postgres/test/Eventuous.Tests.Postgres/Projections/ProjectorTests.cs @@ -1,6 +1,3 @@ -// Copyright (C) Ubiquitous AS. All rights reserved -// Licensed under the Apache License, Version 2.0. - using Eventuous.Postgresql.Projections; using Eventuous.Postgresql.Subscriptions; using Eventuous.Sut.App; @@ -8,12 +5,12 @@ using Eventuous.Tests.Persistence.Base.Fixtures; using Eventuous.Tests.Postgres.Subscriptions; using Npgsql; +using Assert = TUnit.Assertions.Assert; namespace Eventuous.Tests.Postgres.Projections; -[Collection("Database")] -public class ProjectorTests(ITestOutputHelper outputHelper) : IAsyncLifetime { - readonly SubscriptionFixture _fixture = new(_ => { }, outputHelper); +public class ProjectorTests() { + readonly SubscriptionFixture _fixture = new(_ => { }); const string Schema = """ create table if not exists __schema__.bookings ( @@ -23,24 +20,24 @@ price numeric(10,2) ); """; - [Fact] - public async Task ProjectImportedBookingsToTable() { + [Test] + public async Task ProjectImportedBookingsToTable(CancellationToken cancellationToken) { await CreateSchema(); var commands = await GenerateAndProduceEvents(100); - await Task.Delay(1000); + await Task.Delay(1000, cancellationToken); - await using var connection = await _fixture.DataSource.OpenConnectionAsync(); + await using var connection = await _fixture.DataSource.OpenConnectionAsync(cancellationToken); var select = $"select * from {_fixture.SchemaName}.bookings where booking_id = @bookingId"; foreach (var command in commands) { await using var cmd = new NpgsqlCommand(select, connection); cmd.Parameters.AddWithValue("@bookingId", command.BookingId); - await using var reader = await cmd.ExecuteReaderAsync(); - await reader.ReadAsync(); - reader["checkin_date"].Should().Be(command.CheckIn.ToDateTimeUnspecified()); - reader["price"].Should().Be(command.Price); + await using var reader = await cmd.ExecuteReaderAsync(cancellationToken); + await reader.ReadAsync(cancellationToken); + await Assert.That(reader["checkin_date"]).IsEqualTo(command.CheckIn.ToDateTimeUnspecified()); + await Assert.That(reader["price"]).IsEqualTo((decimal)command.Price); } } @@ -71,9 +68,11 @@ async Task CreateSchema() { static BookingEvents.BookingImported ToEvent(Commands.ImportBooking cmd) => new(cmd.RoomId, cmd.Price, cmd.CheckIn, cmd.CheckOut); - public Task InitializeAsync() => _fixture.InitializeAsync(); + [Before(Test)] + public async ValueTask InitializeAsync() => await _fixture.InitializeAsync(); - public Task DisposeAsync() => _fixture.DisposeAsync(); + [After(Test)] + public async ValueTask DisposeAsync() => await _fixture.DisposeAsync(); } public class TestProjector : PostgresProjector { diff --git a/src/Postgres/test/Eventuous.Tests.Postgres/Registrations/RegistrationTests.cs b/src/Postgres/test/Eventuous.Tests.Postgres/Registrations/RegistrationTests.cs index d82754bbf..7955298cc 100644 --- a/src/Postgres/test/Eventuous.Tests.Postgres/Registrations/RegistrationTests.cs +++ b/src/Postgres/test/Eventuous.Tests.Postgres/Registrations/RegistrationTests.cs @@ -4,14 +4,15 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Npgsql; +using Assert = TUnit.Assertions.Assert; namespace Eventuous.Tests.Postgres.Registrations; public class RegistrationTests { const string ConnectionString = "Host=localhost;Username=postgres;Password=secret;Database=eventuous;"; - [Fact] - public void Should_resolve_store_with_manual_registration() { + [Test] + public async Task Should_resolve_store_with_manual_registration() { var ds = new NpgsqlDataSourceBuilder(ConnectionString).Build(); var builder = new WebHostBuilder(); builder.Configure(_ => { }); @@ -25,11 +26,11 @@ public void Should_resolve_store_with_manual_registration() { ); var app = builder.Build(); var aggregateStore = app.Services.GetRequiredService(); - aggregateStore.Should().NotBeNull(); + await Assert.That(aggregateStore).IsNotNull(); } - [Fact] - public void Should_resolve_store_with_extensions() { + [Test] + public async Task Should_resolve_store_with_extensions() { var builder = new WebHostBuilder(); var config = new Dictionary { ["postgres:schema"] = "test", @@ -47,7 +48,7 @@ public void Should_resolve_store_with_extensions() { var app = builder.Build(); var reader = app.Services.GetService(); var npgSqlReader = ((reader as TracedEventStore)!).Inner as PostgresStore; - npgSqlReader.Should().NotBeNull(); - npgSqlReader!.Schema.StreamMessage.Should().Be("test.stream_message"); + await Assert.That(npgSqlReader).IsNotNull(); + await Assert.That(npgSqlReader!.Schema.StreamMessage).IsEqualTo("test.stream_message"); } } diff --git a/src/Postgres/test/Eventuous.Tests.Postgres/Store/StoreTests.cs b/src/Postgres/test/Eventuous.Tests.Postgres/Store/StoreTests.cs index d11bfc069..a8cf87223 100644 --- a/src/Postgres/test/Eventuous.Tests.Postgres/Store/StoreTests.cs +++ b/src/Postgres/test/Eventuous.Tests.Postgres/Store/StoreTests.cs @@ -4,11 +4,14 @@ namespace Eventuous.Tests.Postgres.Store; -[Collection("Database")] +[InheritsTests] +[ClassDataSource] public class Append(StoreFixture fixture) : StoreAppendTests(fixture); -[Collection("Database")] +[InheritsTests] +[ClassDataSource] public class Read(StoreFixture fixture) : StoreReadTests(fixture); -[Collection("Database")] +[InheritsTests] +[ClassDataSource] public class OtherMethods(StoreFixture fixture) : StoreOtherOpsTests(fixture); \ No newline at end of file diff --git a/src/Postgres/test/Eventuous.Tests.Postgres/Store/TieredStoreTests.cs b/src/Postgres/test/Eventuous.Tests.Postgres/Store/TieredStoreTests.cs index ee241deb5..9da0a6e78 100644 --- a/src/Postgres/test/Eventuous.Tests.Postgres/Store/TieredStoreTests.cs +++ b/src/Postgres/test/Eventuous.Tests.Postgres/Store/TieredStoreTests.cs @@ -1,8 +1,6 @@ using Eventuous.Tests.Persistence.Base.Store; -using JetBrains.Annotations; using Testcontainers.PostgreSql; namespace Eventuous.Tests.Postgres.Store; -[UsedImplicitly] -public class TieredStoreTests(StoreFixture storeFixture) : TieredStoreTestsBase(storeFixture), IClassFixture; +public class TieredStoreTests(StoreFixture storeFixture) : TieredStoreTestsBase(storeFixture); diff --git a/src/Postgres/test/Eventuous.Tests.Postgres/Subscriptions/SubscribeTests.cs b/src/Postgres/test/Eventuous.Tests.Postgres/Subscriptions/SubscribeTests.cs index 5ce296299..8051d0d27 100644 --- a/src/Postgres/test/Eventuous.Tests.Postgres/Subscriptions/SubscribeTests.cs +++ b/src/Postgres/test/Eventuous.Tests.Postgres/Subscriptions/SubscribeTests.cs @@ -6,53 +6,48 @@ namespace Eventuous.Tests.Postgres.Subscriptions; -[Collection("Database")] -public class SubscribeToAll(ITestOutputHelper outputHelper) +public class SubscribeToAll() : SubscribeToAllBase( - outputHelper, - new SubscriptionFixture(_ => { }, outputHelper, false) + new SubscriptionFixture(_ => { }, false) ) { - [Fact] - public async Task Postgres_ShouldConsumeProducedEvents() { - await ShouldConsumeProducedEvents(); + [Test] + public async Task Postgres_ShouldConsumeProducedEvents(CancellationToken cancellationToken) { + await ShouldConsumeProducedEvents(cancellationToken); } - [Fact] - public async Task Postgres_ShouldConsumeProducedEventsWhenRestarting() { - await ShouldConsumeProducedEventsWhenRestarting(); + [Test] + public async Task Postgres_ShouldConsumeProducedEventsWhenRestarting(CancellationToken cancellationToken) { + await ShouldConsumeProducedEventsWhenRestarting(cancellationToken); } - [Fact] - public async Task Postgres_ShouldUseExistingCheckpoint() { - await ShouldUseExistingCheckpoint(); + [Test] + public async Task Postgres_ShouldUseExistingCheckpoint(CancellationToken cancellationToken) { + await ShouldUseExistingCheckpoint(cancellationToken); } } -[Collection("Database")] -public class SubscribeToStream(ITestOutputHelper outputHelper, StreamNameFixture streamNameFixture) +[ClassDataSource] +public class SubscribeToStream(StreamNameFixture streamNameFixture) : SubscribeToStreamBase( - outputHelper, - streamNameFixture.StreamName, - new SubscriptionFixture( - opt => ConfigureOptions(opt, streamNameFixture), - outputHelper, - false - ) - ), - IClassFixture { - [Fact] - public async Task Postgres_ShouldConsumeProducedEvents() { - await ShouldConsumeProducedEvents(); + streamNameFixture.StreamName, + new SubscriptionFixture( + opt => ConfigureOptions(opt, streamNameFixture), + false + ) + ) { + [Test] + public async Task Postgres_ShouldConsumeProducedEvents(CancellationToken cancellationToken) { + await ShouldConsumeProducedEvents(cancellationToken); } - [Fact] - public async Task Postgres_ShouldConsumeProducedEventsWhenRestarting() { - await ShouldConsumeProducedEventsWhenRestarting(); + [Test] + public async Task Postgres_ShouldConsumeProducedEventsWhenRestarting(CancellationToken cancellationToken) { + await ShouldConsumeProducedEventsWhenRestarting(cancellationToken); } - [Fact] - public async Task Postgres_ShouldUseExistingCheckpoint() { - await ShouldUseExistingCheckpoint(); + [Test] + public async Task Postgres_ShouldUseExistingCheckpoint(CancellationToken cancellationToken) { + await ShouldUseExistingCheckpoint(cancellationToken); } static void ConfigureOptions(PostgresStreamSubscriptionOptions options, StreamNameFixture streamNameFixture) { diff --git a/src/Postgres/test/Eventuous.Tests.Postgres/Subscriptions/SubscriptionFixture.cs b/src/Postgres/test/Eventuous.Tests.Postgres/Subscriptions/SubscriptionFixture.cs index 074acb608..33a3549cd 100644 --- a/src/Postgres/test/Eventuous.Tests.Postgres/Subscriptions/SubscriptionFixture.cs +++ b/src/Postgres/test/Eventuous.Tests.Postgres/Subscriptions/SubscriptionFixture.cs @@ -11,13 +11,11 @@ namespace Eventuous.Tests.Postgres.Subscriptions; public class SubscriptionFixture( Action configureOptions, - ITestOutputHelper outputHelper, bool autoStart = true, Action? configureServices = null, LogLevel logLevel = LogLevel.Debug ) : SubscriptionFixtureBase( - outputHelper, autoStart, logLevel ) @@ -26,8 +24,6 @@ public class SubscriptionFixture PostgresContainer.Create(); protected override PostgresCheckpointStore GetCheckpointStore(IServiceProvider sp) @@ -43,7 +39,7 @@ protected override void SetupServices(IServiceCollection services) { services.AddSingleton(new SchemaInfo(SchemaName)); services.AddEventuousPostgres(Container.GetConnectionString(), SchemaName, true); services.AddEventStore(); - services.AddSingleton(new TestEventHandlerOptions(null, _outputHelper)); + services.AddSingleton(new TestEventHandlerOptions()); services.AddPostgresCheckpointStore(); configureServices?.Invoke(services); } diff --git a/src/RabbitMq/test/Eventuous.Tests.RabbitMq/Eventuous.Tests.RabbitMq.csproj b/src/RabbitMq/test/Eventuous.Tests.RabbitMq/Eventuous.Tests.RabbitMq.csproj index fc8c59006..aa2febb01 100644 --- a/src/RabbitMq/test/Eventuous.Tests.RabbitMq/Eventuous.Tests.RabbitMq.csproj +++ b/src/RabbitMq/test/Eventuous.Tests.RabbitMq/Eventuous.Tests.RabbitMq.csproj @@ -1,12 +1,14 @@ - - + + - + - + + + diff --git a/src/RabbitMq/test/Eventuous.Tests.RabbitMq/RabbitMqFixture.cs b/src/RabbitMq/test/Eventuous.Tests.RabbitMq/RabbitMqFixture.cs index 7525b122a..a14c2b8a2 100644 --- a/src/RabbitMq/test/Eventuous.Tests.RabbitMq/RabbitMqFixture.cs +++ b/src/RabbitMq/test/Eventuous.Tests.RabbitMq/RabbitMqFixture.cs @@ -1,8 +1,9 @@ using Testcontainers.RabbitMq; +using TUnit.Core.Interfaces; namespace Eventuous.Tests.RabbitMq; -public class RabbitMqFixture : IAsyncLifetime { +public class RabbitMqFixture : IAsyncInitializer, IAsyncDisposable { RabbitMqContainer _rabbitMq = null!; public ConnectionFactory ConnectionFactory { get; private set; } = null!; @@ -10,8 +11,8 @@ public class RabbitMqFixture : IAsyncLifetime { public async Task InitializeAsync() { _rabbitMq = new RabbitMqBuilder().Build(); await _rabbitMq.StartAsync(); - ConnectionFactory = new ConnectionFactory { Uri = new Uri(_rabbitMq.GetConnectionString()), DispatchConsumersAsync = true }; + ConnectionFactory = new() { Uri = new(_rabbitMq.GetConnectionString()), DispatchConsumersAsync = true }; } - public async Task DisposeAsync() => await _rabbitMq.DisposeAsync(); + public async ValueTask DisposeAsync() => await _rabbitMq.DisposeAsync(); } diff --git a/src/RabbitMq/test/Eventuous.Tests.RabbitMq/SubscriptionSpec.cs b/src/RabbitMq/test/Eventuous.Tests.RabbitMq/SubscriptionSpec.cs index 109ff98a3..dc47f90b0 100644 --- a/src/RabbitMq/test/Eventuous.Tests.RabbitMq/SubscriptionSpec.cs +++ b/src/RabbitMq/test/Eventuous.Tests.RabbitMq/SubscriptionSpec.cs @@ -2,12 +2,14 @@ using Eventuous.RabbitMq.Producers; using Eventuous.RabbitMq.Subscriptions; using Eventuous.Subscriptions.Filters; -using Eventuous.TestHelpers; +using Eventuous.TestHelpers.TUnit; +using Eventuous.TestHelpers.TUnit.Logging; using Eventuous.Tests.Subscriptions.Base; namespace Eventuous.Tests.RabbitMq; -public class SubscriptionSpec : IAsyncLifetime, IClassFixture { +[ClassDataSource] +public class SubscriptionSpec { static SubscriptionSpec() => TypeMap.Instance.RegisterKnownEventTypes(typeof(TestEvent).Assembly); static readonly Fixture Auto = new(); @@ -21,38 +23,38 @@ public class SubscriptionSpec : IAsyncLifetime, IClassFixture { readonly ILoggerFactory _loggerFactory; readonly RabbitMqFixture _fixture; - public SubscriptionSpec(RabbitMqFixture fixture, ITestOutputHelper outputHelper) { + public SubscriptionSpec(RabbitMqFixture fixture) { _fixture = fixture; - _es = new TestEventListener(outputHelper); - _exchange = new StreamName(Auto.Create()); - _loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug).AddXunit(outputHelper, LogLevel.Trace)); - - _log = _loggerFactory.CreateLogger(); + _es = new(); + _exchange = new(Auto.Create()); + _loggerFactory = LoggingExtensions.GetLoggerFactory(); + _log = _loggerFactory.CreateLogger(); } - [Fact] - public async Task SubscribeAndProduce() { + [Test] + public async Task SubscribeAndProduce(CancellationToken cancellationToken) { var testEvent = Auto.Create(); - await _producer.Produce(_exchange, testEvent, new Metadata()); - await _handler.AssertThat().Timebox(10.Seconds()).Any().Match(x => x as TestEvent == testEvent).Validate(); + await _producer.Produce(_exchange, testEvent, new(), cancellationToken: cancellationToken); + await _handler.AssertThat().Timebox(10.Seconds()).Any().Match(x => x as TestEvent == testEvent).Validate(cancellationToken); } - [Fact] - public async Task SubscribeAndProduceMany() { + [Test] + public async Task SubscribeAndProduceMany(CancellationToken cancellationToken) { const int count = 10000; var testEvents = Auto.CreateMany(count).ToList(); - await _producer.Produce(_exchange, testEvents, new Metadata()); - await _handler.AssertCollection(10.Seconds(), [..testEvents]).Validate(); + await _producer.Produce(_exchange, testEvents, new(), cancellationToken: cancellationToken); + await _handler.AssertCollection(30.Seconds(), [..testEvents]).Validate(cancellationToken); } - public async Task InitializeAsync() { - _handler = new TestEventHandler(); - _producer = new RabbitMqProducer(_fixture.ConnectionFactory); + [Before(Test)] + public async ValueTask InitializeAsync() { + _handler = new(); + _producer = new(_fixture.ConnectionFactory); var queue = Auto.Create(); - _subscription = new RabbitMqSubscription( + _subscription = new( _fixture.ConnectionFactory, new RabbitMqSubscriptionOptions { ConcurrencyLimit = 10, @@ -67,7 +69,8 @@ public async Task InitializeAsync() { await _producer.StartAsync(); } - public async Task DisposeAsync() { + [After(Test)] + public async ValueTask DisposeAsync() { await _producer.StopAsync(); await _subscription.UnsubscribeWithLog(_log); _es.Dispose(); diff --git a/src/Redis/test/Eventuous.Tests.Redis/Eventuous.Tests.Redis.csproj b/src/Redis/test/Eventuous.Tests.Redis/Eventuous.Tests.Redis.csproj index c03cc857f..1c4a49dd8 100644 --- a/src/Redis/test/Eventuous.Tests.Redis/Eventuous.Tests.Redis.csproj +++ b/src/Redis/test/Eventuous.Tests.Redis/Eventuous.Tests.Redis.csproj @@ -1,16 +1,17 @@ true + Exe - - - - - + + + + + - - - + + + diff --git a/src/Redis/test/Eventuous.Tests.Redis/Fixtures/DomainFixture.cs b/src/Redis/test/Eventuous.Tests.Redis/Fixtures/DomainFixture.cs index 0d72d7f21..f5fd0c50c 100644 --- a/src/Redis/test/Eventuous.Tests.Redis/Fixtures/DomainFixture.cs +++ b/src/Redis/test/Eventuous.Tests.Redis/Fixtures/DomainFixture.cs @@ -1,4 +1,5 @@ using Eventuous.Sut.App; +using Eventuous.Sut.Domain; using MicroElements.AutoFixture.NodaTime; using NodaTime; @@ -7,8 +8,7 @@ namespace Eventuous.Tests.Redis.Fixtures; public static class DomainFixture { static IFixture Auto { get; } = new Fixture().Customize(new NodaTimeCustomization()); - static DomainFixture() - => TypeMap.RegisterKnownEventTypes(); + static DomainFixture() => TypeMap.RegisterKnownEventTypes(typeof(BookingEvents.BookingImported).Assembly); public static Commands.ImportBooking CreateImportBooking() { var from = Auto.Create(); diff --git a/src/Redis/test/Eventuous.Tests.Redis/Fixtures/IntegrationFixture.cs b/src/Redis/test/Eventuous.Tests.Redis/Fixtures/IntegrationFixture.cs index b9420392e..a4600945d 100644 --- a/src/Redis/test/Eventuous.Tests.Redis/Fixtures/IntegrationFixture.cs +++ b/src/Redis/test/Eventuous.Tests.Redis/Fixtures/IntegrationFixture.cs @@ -4,10 +4,11 @@ using Eventuous.Redis; using Eventuous.TestHelpers; using Testcontainers.Redis; +using TUnit.Core.Interfaces; namespace Eventuous.Tests.Redis.Fixtures; -public sealed class IntegrationFixture : IAsyncLifetime { +public sealed class IntegrationFixture : IAsyncInitializer, IAsyncDisposable { public IEventWriter EventWriter { get; private set; } = null!; public IEventReader EventReader { get; private set; } = null!; public GetRedisDatabase GetDatabase { get; private set; } = null!; @@ -17,9 +18,7 @@ public sealed class IntegrationFixture : IAsyncLifetime { IEventSerializer Serializer { get; } = new DefaultEventSerializer(TestPrimitives.DefaultOptions); - public IntegrationFixture() { - DefaultEventSerializer.SetDefaultSerializer(Serializer); - } + public IntegrationFixture() => DefaultEventSerializer.SetDefaultSerializer(Serializer); public async Task InitializeAsync() { _redisContainer = new RedisBuilder().WithImage("redis:7.0.12-alpine").Build(); @@ -42,7 +41,7 @@ IDatabase GetDb() { } } - public async Task DisposeAsync() { + public async ValueTask DisposeAsync() { await _redisContainer.DisposeAsync(); _listener.Dispose(); } diff --git a/src/Redis/test/Eventuous.Tests.Redis/Fixtures/SubscriptionFixture.cs b/src/Redis/test/Eventuous.Tests.Redis/Fixtures/SubscriptionFixture.cs index bd56ac9ce..c16843b57 100644 --- a/src/Redis/test/Eventuous.Tests.Redis/Fixtures/SubscriptionFixture.cs +++ b/src/Redis/test/Eventuous.Tests.Redis/Fixtures/SubscriptionFixture.cs @@ -2,48 +2,49 @@ using Eventuous.Redis.Subscriptions; using Eventuous.Subscriptions; using Eventuous.Subscriptions.Filters; +using Eventuous.TestHelpers.TUnit.Logging; using Eventuous.Tests.Subscriptions.Base; namespace Eventuous.Tests.Redis.Fixtures; -public abstract class SubscriptionFixture : IAsyncLifetime where T : class, IEventHandler { +public class SubscriptionFixture where T : IEventHandler, new() { static SubscriptionFixture() => TypeMap.Instance.RegisterKnownEventTypes(typeof(TestEvent).Assembly); - protected IntegrationFixture IntegrationFixture { get; private set; } = null!; - protected StreamName Stream { get; } - protected T Handler { get; private set; } = null!; + public IntegrationFixture IntegrationFixture { get; private set; } = null!; + public StreamName Stream { get; } protected ILogger Log { get; } - protected RedisCheckpointStore CheckpointStore { get; private set; } = null!; - protected ILoggerFactory LoggerFactory { get; } + public RedisCheckpointStore CheckpointStore { get; private set; } = null!; + public ILoggerFactory LoggerFactory { get; } + public T Handler { get; } IMessageSubscription Subscription { get; set; } = null!; - protected SubscriptionFixture(ITestOutputHelper outputHelper, bool subscribeToAll, bool autoStart = true, LogLevel logLevel = LogLevel.Trace) { + public SubscriptionFixture(bool subscribeToAll, LogLevel logLevel = LogLevel.Trace) { + Handler = new T(); _subscribeToAll = subscribeToAll; - _autoStart = autoStart; - Stream = new StreamName(SharedAutoFixture.Auto.Create()); - LoggerFactory = TestHelpers.Logging.GetLoggerFactory(outputHelper, logLevel); + Stream = new(SharedAutoFixture.Auto.Create()); + LoggerFactory = LoggingExtensions.GetLoggerFactory(logLevel); SubscriptionId = $"test-{Guid.NewGuid():N}"; Log = LoggerFactory.CreateLogger(GetType()); - _listener = new LoggingEventListener(LoggerFactory); + _listener = new(LoggerFactory); } - protected abstract T GetHandler(); - public string SubscriptionId { get; } - protected ValueTask Start() => Subscription.SubscribeWithLog(Log); + public async Task Start() { + await Subscription.SubscribeWithLog(Log); + } - protected ValueTask Stop() => Subscription.UnsubscribeWithLog(Log); + public async Task Stop() { + await Subscription.UnsubscribeWithLog(Log); + } readonly bool _subscribeToAll; - readonly bool _autoStart; readonly LoggingEventListener _listener; - public async Task InitializeAsync() { + public async ValueTask InitializeAsync() { IntegrationFixture = new(); await IntegrationFixture.InitializeAsync(); - Handler = GetHandler(); - CheckpointStore = new RedisCheckpointStore(IntegrationFixture.GetDatabase, LoggerFactory); + CheckpointStore = new(IntegrationFixture.GetDatabase, LoggerFactory); var pipe = new ConsumePipe(); pipe.AddDefaultConsumer(Handler); @@ -52,29 +53,27 @@ public async Task InitializeAsync() { !_subscribeToAll ? new RedisStreamSubscription( IntegrationFixture.GetDatabase, - new RedisStreamSubscriptionOptions(Stream) { SubscriptionId = SubscriptionId }, + new(Stream) { SubscriptionId = SubscriptionId }, CheckpointStore, pipe, LoggerFactory ) : new RedisAllStreamSubscription( IntegrationFixture.GetDatabase, - new RedisAllStreamSubscriptionOptions { SubscriptionId = SubscriptionId }, + new() { SubscriptionId = SubscriptionId }, CheckpointStore, pipe, LoggerFactory ); - if (_autoStart) await Start(); } - public async Task DisposeAsync() { - if (_autoStart) await Stop(); - await FlushDB(); + public async ValueTask DisposeAsync() { + await FlushDb(); _listener.Dispose(); await IntegrationFixture.DisposeAsync(); } - async Task FlushDB() { + async Task FlushDb() { var database = IntegrationFixture.GetDatabase(); await database.ExecuteAsync("FLUSHDB"); } diff --git a/src/Redis/test/Eventuous.Tests.Redis/Store/Append.cs b/src/Redis/test/Eventuous.Tests.Redis/Store/Append.cs index 491849a53..ef822e19c 100644 --- a/src/Redis/test/Eventuous.Tests.Redis/Store/Append.cs +++ b/src/Redis/test/Eventuous.Tests.Redis/Store/Append.cs @@ -3,51 +3,52 @@ namespace Eventuous.Tests.Redis.Store; -public class AppendEvents(IntegrationFixture fixture) : IClassFixture { - [Fact] - public async Task ShouldAppendToNoStream() { +[ClassDataSource] +public class AppendEvents(IntegrationFixture fixture) { + [Test] + public async Task ShouldAppendToNoStream(CancellationToken cancellationToken) { var evt = CreateEvent(); var streamName = GetStreamName(); - var result = await fixture.AppendEvent(streamName, evt, ExpectedStreamVersion.NoStream); + var result = await fixture.AppendEvent(streamName, evt, ExpectedStreamVersion.NoStream, cancellationToken); result.NextExpectedVersion.Should().Be(0); } - [Fact] - public async Task ShouldAppendOneByOne() { + [Test] + public async Task ShouldAppendOneByOne(CancellationToken cancellationToken) { var evt = CreateEvent(); var stream = GetStreamName(); - var result = await fixture.AppendEvent(stream, evt, ExpectedStreamVersion.NoStream); + var result = await fixture.AppendEvent(stream, evt, ExpectedStreamVersion.NoStream, cancellationToken); evt = CreateEvent(); var version = new ExpectedStreamVersion(result.NextExpectedVersion); - result = await fixture.AppendEvent(stream, evt, version); + result = await fixture.AppendEvent(stream, evt, version, cancellationToken); result.NextExpectedVersion.Should().Be(1); } - [Fact] - public async Task ShouldFailOnWrongVersionNoStream() { + [Test] + public async Task ShouldFailOnWrongVersionNoStream(CancellationToken cancellationToken) { var evt = CreateEvent(); var stream = GetStreamName(); - await fixture.AppendEvent(stream, evt, ExpectedStreamVersion.NoStream); + await fixture.AppendEvent(stream, evt, ExpectedStreamVersion.NoStream, cancellationToken); evt = CreateEvent(); - var task = () => fixture.AppendEvent(stream, evt, ExpectedStreamVersion.NoStream); + var task = () => fixture.AppendEvent(stream, evt, ExpectedStreamVersion.NoStream, cancellationToken); await task.Should().ThrowAsync(); } - [Fact] - public async Task ShouldFailOnWrongVersion() { + [Test] + public async Task ShouldFailOnWrongVersion(CancellationToken cancellationToken) { var evt = CreateEvent(); var stream = GetStreamName(); - await fixture.AppendEvent(stream, evt, ExpectedStreamVersion.NoStream); + await fixture.AppendEvent(stream, evt, ExpectedStreamVersion.NoStream, cancellationToken); evt = CreateEvent(); - var task = () => fixture.AppendEvent(stream, evt, new ExpectedStreamVersion(3)); + var task = () => fixture.AppendEvent(stream, evt, new(3), cancellationToken); await task.Should().ThrowAsync(); } } diff --git a/src/Redis/test/Eventuous.Tests.Redis/Store/Helpers.cs b/src/Redis/test/Eventuous.Tests.Redis/Store/Helpers.cs index d4ad88da3..5a17bb584 100644 --- a/src/Redis/test/Eventuous.Tests.Redis/Store/Helpers.cs +++ b/src/Redis/test/Eventuous.Tests.Redis/Store/Helpers.cs @@ -24,16 +24,17 @@ public static Task AppendEvents( this IntegrationFixture fixture, StreamName stream, object[] evt, - ExpectedStreamVersion version + ExpectedStreamVersion version, + CancellationToken cancellationToken ) { var streamEvents = evt.Select(x => new NewStreamEvent(Guid.NewGuid(), x, new())); - return fixture.EventWriter.AppendEvents(stream, version, streamEvents.ToArray(), default); + return fixture.EventWriter.AppendEvents(stream, version, streamEvents.ToArray(), cancellationToken); } - public static Task AppendEvent(this IntegrationFixture fixture, StreamName stream, object evt, ExpectedStreamVersion version) { + public static Task AppendEvent(this IntegrationFixture fixture, StreamName stream, object evt, ExpectedStreamVersion version, CancellationToken cancellationToken) { var streamEvent = new NewStreamEvent(Guid.NewGuid(), evt, new()); - return fixture.EventWriter.AppendEvents(stream, version, [streamEvent], default); + return fixture.EventWriter.AppendEvents(stream, version, [streamEvent], cancellationToken); } } diff --git a/src/Redis/test/Eventuous.Tests.Redis/Store/Read.cs b/src/Redis/test/Eventuous.Tests.Redis/Store/Read.cs index dbdc8a76c..947205123 100644 --- a/src/Redis/test/Eventuous.Tests.Redis/Store/Read.cs +++ b/src/Redis/test/Eventuous.Tests.Redis/Store/Read.cs @@ -3,58 +3,59 @@ namespace Eventuous.Tests.Redis.Store; -public class ReadEvents(IntegrationFixture fixture) : IClassFixture { - [Fact] - public async Task ShouldReadOne() { +[ClassDataSource] +public class ReadEvents(IntegrationFixture fixture) { + [Test] + public async Task ShouldReadOne(CancellationToken cancellationToken) { var evt = CreateEvent(); var streamName = GetStreamName(); - await fixture.AppendEvent(streamName, evt, ExpectedStreamVersion.NoStream); + await fixture.AppendEvent(streamName, evt, ExpectedStreamVersion.NoStream, cancellationToken); - var result = await fixture.EventReader.ReadEvents(streamName, StreamReadPosition.Start, 100, default); + var result = await fixture.EventReader.ReadEvents(streamName, StreamReadPosition.Start, 100, cancellationToken); result.Length.Should().Be(1); result[0].Payload.Should().BeEquivalentTo(evt); } - [Fact] - public async Task ShouldReadMany() { + [Test] + public async Task ShouldReadMany(CancellationToken cancellationToken) { // ReSharper disable once CoVariantArrayConversion var events = CreateEvents(20).ToArray(); var streamName = GetStreamName(); - await fixture.AppendEvents(streamName, events, ExpectedStreamVersion.NoStream); + await fixture.AppendEvents(streamName, events, ExpectedStreamVersion.NoStream, cancellationToken); - var result = await fixture.EventReader.ReadEvents(streamName, StreamReadPosition.Start, 100, default); + var result = await fixture.EventReader.ReadEvents(streamName, StreamReadPosition.Start, 100, cancellationToken); var actual = result.Select(x => x.Payload); actual.Should().BeEquivalentTo(events); } - [Fact] - public async Task ShouldReadTail() { + [Test] + public async Task ShouldReadTail(CancellationToken cancellationToken) { // ReSharper disable once CoVariantArrayConversion var streamName = GetStreamName(); var events1 = CreateEvents(10).ToArray(); - var appended = await fixture.AppendEvents(streamName, events1, ExpectedStreamVersion.NoStream); + var appended = await fixture.AppendEvents(streamName, events1, ExpectedStreamVersion.NoStream, cancellationToken); var position = appended.GlobalPosition; var events2 = CreateEvents(10).ToArray(); - await fixture.AppendEvents(streamName, events2, ExpectedStreamVersion.Any); + await fixture.AppendEvents(streamName, events2, ExpectedStreamVersion.Any, cancellationToken); - var result = await fixture.EventReader.ReadEvents(streamName, new StreamReadPosition((long)position), 100, default); + var result = await fixture.EventReader.ReadEvents(streamName, new((long)position), 100, cancellationToken); var actual = result.Select(x => x.Payload); actual.Should().BeEquivalentTo(events2); } - [Fact] - public async Task ShouldReadHead() { + [Test] + public async Task ShouldReadHead(CancellationToken cancellationToken) { // ReSharper disable once CoVariantArrayConversion var events = CreateEvents(20).ToArray(); var streamName = GetStreamName(); - await fixture.AppendEvents(streamName, events, ExpectedStreamVersion.NoStream); + await fixture.AppendEvents(streamName, events, ExpectedStreamVersion.NoStream, cancellationToken); - var result = await fixture.EventReader.ReadEvents(streamName, StreamReadPosition.Start, 10, default); + var result = await fixture.EventReader.ReadEvents(streamName, StreamReadPosition.Start, 10, cancellationToken); var expected = events.Take(10); var actual = result.Select(x => x.Payload); diff --git a/src/Redis/test/Eventuous.Tests.Redis/Subscriptions/SubscribeToAll.cs b/src/Redis/test/Eventuous.Tests.Redis/Subscriptions/SubscribeToAll.cs index 74e2feea6..e2b89fffd 100644 --- a/src/Redis/test/Eventuous.Tests.Redis/Subscriptions/SubscribeToAll.cs +++ b/src/Redis/test/Eventuous.Tests.Redis/Subscriptions/SubscribeToAll.cs @@ -1,4 +1,3 @@ -using Eventuous.Subscriptions.Checkpoints; using Eventuous.Subscriptions.Logging; using Eventuous.Tests.Redis.Fixtures; using Eventuous.Tests.Subscriptions.Base; @@ -7,26 +6,42 @@ namespace Eventuous.Tests.Redis.Subscriptions; -public class SubscribeToAll(ITestOutputHelper outputHelper) : SubscriptionFixture(outputHelper, true, false) { - [Fact] - public async Task ShouldConsumeProducedEvents() { +public class SubscribeToAll { + SubscriptionFixture _fixture = null!; + + [Before(Test)] + public async Task Setup() { + _fixture = new(true); + await _fixture.InitializeAsync(); + } + + [After(Test)] + public async Task TearDown() { + await _fixture.DisposeAsync(); + } + + [Test] + [Retry(5)] + public async Task ShouldConsumeProducedEvents(CancellationToken cancellationToken) { const int count = 10; var (testEvents, _) = await GenerateAndProduceEvents(count); - await Start(); - await Handler.AssertThat().Timebox(2.Seconds()).Exactly(count).Match(x => testEvents.Contains(x)).Validate(); - await Stop(); + await _fixture.Start(); + await _fixture.Handler.AssertThat().Timebox(2.Seconds()).Exactly(count).Match(x => testEvents.Contains(x)).Validate(cancellationToken); + await _fixture.Stop(); - Handler.Count.Should().Be(10); + _fixture.Handler.Count.Should().Be(10); } - [Fact] - public async Task ShouldConsumeProducedEventsWhenRestarting() { + [Test] + [Retry(5)] + public async Task ShouldConsumeProducedEventsWhenRestarting(CancellationToken cancellationToken) { await TestConsumptionOfProducedEvents(); - Handler.Reset(); - await InitializeAsync(); + _fixture.Handler.Reset(); + + await _fixture.InitializeAsync(); await TestConsumptionOfProducedEvents(); @@ -37,28 +52,29 @@ async Task TestConsumptionOfProducedEvents() { var (testEvents, _) = await GenerateAndProduceEvents(count); - await Start(); - await Handler.AssertCollection(2.Seconds(), [..testEvents]).Validate(); - await Stop(); + await _fixture.Start(); + await _fixture.Handler.AssertCollection(2.Seconds(), [..testEvents]).Validate(cancellationToken); + await _fixture.Stop(); - Handler.Count.Should().Be(10); + _fixture.Handler.Count.Should().Be(10); } } - [Fact] - public async Task ShouldUseExistingCheckpoint() { + [Test] + [Retry(5)] + public async Task ShouldUseExistingCheckpoint(CancellationToken cancellationToken) { const int count = 10; var (_, result) = await GenerateAndProduceEvents(count); - await CheckpointStore.GetLastCheckpoint(SubscriptionId, default); - Logger.ConfigureIfNull(SubscriptionId, LoggerFactory); - await CheckpointStore.StoreCheckpoint(new Checkpoint(SubscriptionId, result.GlobalPosition), true, default); + await _fixture.CheckpointStore.GetLastCheckpoint(_fixture.SubscriptionId, cancellationToken); + Logger.ConfigureIfNull(_fixture.SubscriptionId, _fixture.LoggerFactory); + await _fixture.CheckpointStore.StoreCheckpoint(new(_fixture.SubscriptionId, result.GlobalPosition), true, cancellationToken); - await Start(); - await Task.Delay(TimeSpan.FromSeconds(1)); - await Stop(); - Handler.Count.Should().Be(0); + await _fixture.Start(); + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + await _fixture.Stop(); + _fixture.Handler.Count.Should().Be(0); } static BookingImported ToEvent(ImportBooking cmd) @@ -72,10 +88,8 @@ static BookingImported ToEvent(ImportBooking cmd) var events = commands.Select(ToEvent).ToList(); var streamEvents = events.Select(x => new NewStreamEvent(Guid.NewGuid(), x, new())); - var result = await IntegrationFixture.EventWriter.AppendEvents(Stream, ExpectedStreamVersion.Any, streamEvents.ToArray(), default); + var result = await _fixture.IntegrationFixture.EventWriter.AppendEvents(_fixture.Stream, ExpectedStreamVersion.Any, streamEvents.ToArray(), default); return (events, result); } - - protected override TestEventHandler GetHandler() => new(); } diff --git a/src/Redis/test/Eventuous.Tests.Redis/Subscriptions/SubscribeToStream.cs b/src/Redis/test/Eventuous.Tests.Redis/Subscriptions/SubscribeToStream.cs index 5f184bc23..40453fc0b 100644 --- a/src/Redis/test/Eventuous.Tests.Redis/Subscriptions/SubscribeToStream.cs +++ b/src/Redis/test/Eventuous.Tests.Redis/Subscriptions/SubscribeToStream.cs @@ -1,4 +1,3 @@ -using Eventuous.Subscriptions.Checkpoints; using Eventuous.Subscriptions.Logging; using Eventuous.Tests.Redis.Fixtures; using Eventuous.Tests.Subscriptions.Base; @@ -7,26 +6,41 @@ namespace Eventuous.Tests.Redis.Subscriptions; -public class SubscribeToStream(ITestOutputHelper outputHelper) : SubscriptionFixture(outputHelper, false, false) { - [Fact] - public async Task ShouldConsumeProducedEvents() { +public class SubscribeToStream { + SubscriptionFixture _fixture = null!; + + [Before(Test)] + public async Task Setup() { + _fixture = new(false); + await _fixture.InitializeAsync(); + } + + [After(Test)] + public async Task TearDown() { + await _fixture.DisposeAsync(); + } + + [Test] + [Retry(5)] + public async Task ShouldConsumeProducedEvents(CancellationToken cancellationToken) { const int count = 10; var testEvents = await GenerateAndProduceEvents(count); - await Start(); - await Handler.AssertCollection(2.Seconds(), [..testEvents]).Validate(); - await Stop(); + await _fixture.Start(); + await _fixture.Handler.AssertCollection(2.Seconds(), [..testEvents]).Validate(cancellationToken); + await _fixture.Stop(); - Handler.Count.Should().Be(10); + _fixture.Handler.Count.Should().Be(10); } - [Fact] - public async Task ShouldConsumeProducedEventsWhenRestarting() { + [Test] + [Retry(5)] + public async Task ShouldConsumeProducedEventsWhenRestarting(CancellationToken cancellationToken) { await TestConsumptionOfProducedEvents(); - Handler.Reset(); - await InitializeAsync(); + _fixture.Handler.Reset(); + await _fixture.InitializeAsync(); await TestConsumptionOfProducedEvents(); @@ -37,29 +51,30 @@ async Task TestConsumptionOfProducedEvents() { var testEvents = await GenerateAndProduceEvents(count); - await Start(); - await Handler.AssertCollection(2.Seconds(), [..testEvents]).Validate(); - await Stop(); + await _fixture.Start(); + await _fixture.Handler.AssertCollection(2.Seconds(), [..testEvents]).Validate(cancellationToken); + await _fixture.Stop(); - Handler.Count.Should().Be(10); + _fixture.Handler.Count.Should().Be(10); } } - [Fact] - public async Task ShouldUseExistingCheckpoint() { + [Test] + [Retry(5)] + public async Task ShouldUseExistingCheckpoint(CancellationToken cancellationToken) { const int count = 10; await GenerateAndProduceEvents(count); - await CheckpointStore.GetLastCheckpoint(SubscriptionId, default); + await _fixture.CheckpointStore.GetLastCheckpoint(_fixture.SubscriptionId, cancellationToken); var streamPosition = await GetStreamPosition(count); - Logger.ConfigureIfNull(SubscriptionId, LoggerFactory); - await CheckpointStore.StoreCheckpoint(new Checkpoint(SubscriptionId, (ulong)streamPosition), true, default); + Logger.ConfigureIfNull(_fixture.SubscriptionId, _fixture.LoggerFactory); + await _fixture.CheckpointStore.StoreCheckpoint(new(_fixture.SubscriptionId, (ulong)streamPosition), true, cancellationToken); - await Start(); - await Task.Delay(TimeSpan.FromSeconds(1)); - await Stop(); - Handler.Count.Should().Be(0); + await _fixture.Start(); + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + await _fixture.Stop(); + _fixture.Handler.Count.Should().Be(0); } static BookingImported ToEvent(ImportBooking cmd) => new(cmd.RoomId, cmd.Price, cmd.CheckIn, cmd.CheckOut); @@ -73,16 +88,14 @@ async Task> GenerateAndProduceEvents(int count) { var events = commands.Select(ToEvent).ToList(); var streamEvents = events.Select(x => new NewStreamEvent(Guid.NewGuid(), x, new())); - await IntegrationFixture.EventWriter.AppendEvents(Stream, ExpectedStreamVersion.Any, streamEvents.ToArray(), default); + await _fixture.IntegrationFixture.EventWriter.AppendEvents(_fixture.Stream, ExpectedStreamVersion.Any, streamEvents.ToArray(), default); return events; } async Task GetStreamPosition(int count) { - var readEvents = await IntegrationFixture.EventReader.ReadEvents(Stream, StreamReadPosition.Start, count, default); + var readEvents = await _fixture.IntegrationFixture.EventReader.ReadEvents(_fixture.Stream, StreamReadPosition.Start, count, default); return readEvents.Last().Position; } - - protected override TestEventHandler GetHandler() => new(); } diff --git a/src/SqlServer/test/Eventuous.Tests.SqlServer/Eventuous.Tests.SqlServer.csproj b/src/SqlServer/test/Eventuous.Tests.SqlServer/Eventuous.Tests.SqlServer.csproj index 24d5b6f18..aec64d78a 100644 --- a/src/SqlServer/test/Eventuous.Tests.SqlServer/Eventuous.Tests.SqlServer.csproj +++ b/src/SqlServer/test/Eventuous.Tests.SqlServer/Eventuous.Tests.SqlServer.csproj @@ -1,19 +1,24 @@ - - true - true - - - - - - - - - - - - - - + + true + true + Exe + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/SqlServer/test/Eventuous.Tests.SqlServer/Limiter.cs b/src/SqlServer/test/Eventuous.Tests.SqlServer/Limiter.cs new file mode 100644 index 000000000..c029df3a0 --- /dev/null +++ b/src/SqlServer/test/Eventuous.Tests.SqlServer/Limiter.cs @@ -0,0 +1,10 @@ +using Eventuous.Tests.SqlServer; +using TUnit.Core.Interfaces; + +[assembly: ParallelLimiter] + +namespace Eventuous.Tests.SqlServer; + +public class Limiter : IParallelLimit { + public int Limit => 2; +} diff --git a/src/SqlServer/test/Eventuous.Tests.SqlServer/Metrics/MetricsTests.cs b/src/SqlServer/test/Eventuous.Tests.SqlServer/Metrics/MetricsTests.cs index 7cd30a397..8eb321205 100644 --- a/src/SqlServer/test/Eventuous.Tests.SqlServer/Metrics/MetricsTests.cs +++ b/src/SqlServer/test/Eventuous.Tests.SqlServer/Metrics/MetricsTests.cs @@ -1,12 +1,9 @@ -using Eventuous.Sql.Base.Producers; -using Eventuous.SqlServer.Subscriptions; using Eventuous.Tests.OpenTelemetry; -using Testcontainers.SqlEdge; // ReSharper disable UnusedType.Global namespace Eventuous.Tests.SqlServer.Metrics; -[Collection("Database")] -public class MetricsTests(ITestOutputHelper outputHelper) - : MetricsTestsBase(outputHelper); +[ClassDataSource] +[InheritsTests] +public class MetricsTests(MetricsFixture fixture) : MetricsTestsBase(fixture); diff --git a/src/SqlServer/test/Eventuous.Tests.SqlServer/Projections/ProjectorTests.cs b/src/SqlServer/test/Eventuous.Tests.SqlServer/Projections/ProjectorTests.cs index 5ee2ddea3..0f9b428f5 100644 --- a/src/SqlServer/test/Eventuous.Tests.SqlServer/Projections/ProjectorTests.cs +++ b/src/SqlServer/test/Eventuous.Tests.SqlServer/Projections/ProjectorTests.cs @@ -1,7 +1,4 @@ -// Copyright (C) Ubiquitous AS. All rights reserved -// Licensed under the Apache License, Version 2.0. - -using Eventuous.SqlServer; +using Eventuous.SqlServer; using Eventuous.SqlServer.Projections; using Eventuous.SqlServer.Subscriptions; using Eventuous.Sut.App; @@ -12,10 +9,9 @@ namespace Eventuous.Tests.SqlServer.Projections; -[Collection("Database")] -public class ProjectorTests(ITestOutputHelper outputHelper) : IAsyncLifetime { +public class ProjectorTests() { readonly SubscriptionFixture _fixture - = new(_ => { }, outputHelper); + = new(_ => { }); const string Schema = """ IF OBJECT_ID('__schema__.Bookings', 'U') IS NULL @@ -28,14 +24,14 @@ Price NUMERIC(10,2) END """; - [Fact] - public async Task ProjectImportedBookingsToTable() { + [Test] + public async Task ProjectImportedBookingsToTable(CancellationToken cancellationToken) { await CreateSchema(); var commands = await GenerateAndProduceEvents(100); - await Task.Delay(1000); + await Task.Delay(1000, cancellationToken); - await using var connection = await ConnectionFactory.GetConnection(_fixture.ConnectionString, default); + await using var connection = await ConnectionFactory.GetConnection(_fixture.ConnectionString, cancellationToken); var select = $"SELECT * FROM {_fixture.SchemaName}.Bookings where BookingId = @BookingId"; @@ -48,8 +44,8 @@ public async Task ProjectImportedBookingsToTable() { async Task ValidateProjectedObject(SqlConnection conn, Commands.ImportBooking command) { await using var cmd = new SqlCommand(select, conn); cmd.Parameters.AddWithValue("@BookingId", command.BookingId); - await using var reader = await cmd.ExecuteReaderAsync(); - await reader.ReadAsync(); + await using var reader = await cmd.ExecuteReaderAsync(cancellationToken); + await reader.ReadAsync(cancellationToken); reader["CheckinDate"].Should().Be(command.CheckIn.ToDateTimeUnspecified()); reader["Price"].Should().Be(command.Price); } @@ -80,9 +76,11 @@ async Task CreateSchema() { static BookingEvents.BookingImported ToEvent(Commands.ImportBooking cmd) => new(cmd.RoomId, cmd.Price, cmd.CheckIn, cmd.CheckOut); - public Task InitializeAsync() => _fixture.InitializeAsync(); + [Before(Test)] + public async ValueTask InitializeAsync() => await _fixture.InitializeAsync(); - public Task DisposeAsync() => _fixture.DisposeAsync(); + [After(Test)] + public async ValueTask DisposeAsync() => await _fixture.DisposeAsync(); } public class TestProjector : SqlServerProjector { diff --git a/src/SqlServer/test/Eventuous.Tests.SqlServer/Registrations/RegistrationTests.cs b/src/SqlServer/test/Eventuous.Tests.SqlServer/Registrations/RegistrationTests.cs index 9d1c52d9b..b5b582c40 100644 --- a/src/SqlServer/test/Eventuous.Tests.SqlServer/Registrations/RegistrationTests.cs +++ b/src/SqlServer/test/Eventuous.Tests.SqlServer/Registrations/RegistrationTests.cs @@ -9,7 +9,7 @@ namespace Eventuous.Tests.SqlServer.Registrations; public class RegistrationTests { const string ConnectionString = "Server=localhost;User Id=sqlserver;Password=secret;Database=eventuous;TrustServerCertificate=True"; - [Fact] + [Test] public void Should_resolve_store_with_manual_registration() { var builder = new WebHostBuilder(); builder.Configure(_ => { }); @@ -27,7 +27,7 @@ public void Should_resolve_store_with_manual_registration() { innerStore.Should().BeOfType(); } - [Fact] + [Test] public void Should_resolve_store_with_extensions() { var builder = new WebHostBuilder(); diff --git a/src/SqlServer/test/Eventuous.Tests.SqlServer/Store/StoreTests.cs b/src/SqlServer/test/Eventuous.Tests.SqlServer/Store/StoreTests.cs index 54ce965ea..d7578f7cc 100644 --- a/src/SqlServer/test/Eventuous.Tests.SqlServer/Store/StoreTests.cs +++ b/src/SqlServer/test/Eventuous.Tests.SqlServer/Store/StoreTests.cs @@ -4,11 +4,14 @@ namespace Eventuous.Tests.SqlServer.Store; -[Collection("Database")] +[InheritsTests] +[ClassDataSource] public class Append(StoreFixture fixture) : StoreAppendTests(fixture); -[Collection("Database")] +[InheritsTests] +[ClassDataSource] public class Read(StoreFixture fixture) : StoreReadTests(fixture); -[Collection("Database")] +[InheritsTests] +[ClassDataSource] public class OtherMethods(StoreFixture fixture) : StoreOtherOpsTests(fixture); diff --git a/src/SqlServer/test/Eventuous.Tests.SqlServer/Store/TieredStoreTests.cs b/src/SqlServer/test/Eventuous.Tests.SqlServer/Store/TieredStoreTests.cs index 206eb9b67..bb9799fed 100644 --- a/src/SqlServer/test/Eventuous.Tests.SqlServer/Store/TieredStoreTests.cs +++ b/src/SqlServer/test/Eventuous.Tests.SqlServer/Store/TieredStoreTests.cs @@ -1,8 +1,12 @@ using Eventuous.Tests.Persistence.Base.Store; -using JetBrains.Annotations; using Testcontainers.SqlEdge; namespace Eventuous.Tests.SqlServer.Store; -[UsedImplicitly] -public class TieredStoreTests(StoreFixture storeFixture) : TieredStoreTestsBase(storeFixture), IClassFixture; +[ClassDataSource(Shared = SharedType.ForClass)] +public class TieredStoreTests(StoreFixture storeFixture) : TieredStoreTestsBase(storeFixture) { + [Test] + public async Task Should_load_hot_and_archive_test() { + await Should_load_hot_and_archive(); + } +} diff --git a/src/SqlServer/test/Eventuous.Tests.SqlServer/Subscriptions/SubscribeTests.cs b/src/SqlServer/test/Eventuous.Tests.SqlServer/Subscriptions/SubscribeTests.cs index 6f25176b5..f0dc46626 100644 --- a/src/SqlServer/test/Eventuous.Tests.SqlServer/Subscriptions/SubscribeTests.cs +++ b/src/SqlServer/test/Eventuous.Tests.SqlServer/Subscriptions/SubscribeTests.cs @@ -6,53 +6,48 @@ namespace Eventuous.Tests.SqlServer.Subscriptions; -[Collection("Database")] -public class SubscribeToAll(ITestOutputHelper outputHelper) +public class SubscribeToAll() : SubscribeToAllBase( - outputHelper, - new SubscriptionFixture(_ => { }, outputHelper, false) + new SubscriptionFixture(_ => { }, false) ) { - [Fact] - public async Task SqlServer_ShouldConsumeProducedEvents() { - await ShouldConsumeProducedEvents(); + [Test] + public async Task SqlServer_ShouldConsumeProducedEvents(CancellationToken cancellationToken) { + await ShouldConsumeProducedEvents(cancellationToken); } - [Fact] - public async Task SqlServer_ShouldConsumeProducedEventsWhenRestarting() { - await ShouldConsumeProducedEventsWhenRestarting(); + [Test] + public async Task SqlServer_ShouldConsumeProducedEventsWhenRestarting(CancellationToken cancellationToken) { + await ShouldConsumeProducedEventsWhenRestarting(cancellationToken); } - [Fact] - public async Task SqlServer_ShouldUseExistingCheckpoint() { - await ShouldUseExistingCheckpoint(); + [Test] + public async Task SqlServer_ShouldUseExistingCheckpoint(CancellationToken cancellationToken) { + await ShouldUseExistingCheckpoint(cancellationToken); } } -[Collection("Database")] -public class SubscribeToStream(ITestOutputHelper outputHelper, StreamNameFixture streamNameFixture) +[ClassDataSource(Shared = SharedType.None)] +public class SubscribeToStream(StreamNameFixture streamNameFixture) : SubscribeToStreamBase( - outputHelper, - streamNameFixture.StreamName, - new SubscriptionFixture( - opt => ConfigureOptions(opt, streamNameFixture), - outputHelper, - false - ) - ), - IClassFixture { - [Fact] - public async Task SqlServer_ShouldConsumeProducedEvents() { - await ShouldConsumeProducedEvents(); + streamNameFixture.StreamName, + new SubscriptionFixture( + opt => ConfigureOptions(opt, streamNameFixture), + false + ) + ) { + [Test] + public async Task SqlServer_ShouldConsumeProducedEvents(CancellationToken cancellationToken) { + await ShouldConsumeProducedEvents(cancellationToken); } - [Fact] - public async Task SqlServer_ShouldConsumeProducedEventsWhenRestarting() { - await ShouldConsumeProducedEventsWhenRestarting(); + [Test] + public async Task SqlServer_ShouldConsumeProducedEventsWhenRestarting(CancellationToken cancellationToken) { + await ShouldConsumeProducedEventsWhenRestarting(cancellationToken); } - [Fact] - public async Task SqlServer_ShouldUseExistingCheckpoint() { - await ShouldUseExistingCheckpoint(); + [Test] + public async Task SqlServer_ShouldUseExistingCheckpoint(CancellationToken cancellationToken) { + await ShouldUseExistingCheckpoint(cancellationToken); } static void ConfigureOptions(SqlServerStreamSubscriptionOptions options, StreamNameFixture streamNameFixture) { diff --git a/src/SqlServer/test/Eventuous.Tests.SqlServer/Subscriptions/SubscriptionFixture.cs b/src/SqlServer/test/Eventuous.Tests.SqlServer/Subscriptions/SubscriptionFixture.cs index 6da865349..487ae9adf 100644 --- a/src/SqlServer/test/Eventuous.Tests.SqlServer/Subscriptions/SubscriptionFixture.cs +++ b/src/SqlServer/test/Eventuous.Tests.SqlServer/Subscriptions/SubscriptionFixture.cs @@ -11,13 +11,11 @@ namespace Eventuous.Tests.SqlServer.Subscriptions; public class SubscriptionFixture( Action configureOptions, - ITestOutputHelper outputHelper, bool autoStart = true, Action? configureServices = null, LogLevel logLevel = LogLevel.Debug ) : SubscriptionFixtureBase( - outputHelper, autoStart, logLevel ) @@ -26,8 +24,6 @@ public class SubscriptionFixture SqlContainer.Create(); protected override SqlServerCheckpointStore GetCheckpointStore(IServiceProvider sp) @@ -45,7 +41,7 @@ protected override void SetupServices(IServiceCollection services) { services.AddEventuousSqlServer(Container.GetConnectionString(), SchemaName, true); services.AddEventStore(); services.AddSqlServerCheckpointStore(); - services.AddSingleton(new TestEventHandlerOptions(null, _outputHelper)); + services.AddSingleton(new TestEventHandlerOptions()); configureServices?.Invoke(services); } diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 8638ada0b..8ef563fc5 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -1,6 +1,6 @@ - net6.0;net7.0;net8.0 + net9.0;net8.0 false preview enable @@ -14,6 +14,7 @@ true false + diff --git a/test/Eventuous.TestHelpers.TUnit/Eventuous.TestHelpers.TUnit.csproj b/test/Eventuous.TestHelpers.TUnit/Eventuous.TestHelpers.TUnit.csproj new file mode 100644 index 000000000..0c19ddd67 --- /dev/null +++ b/test/Eventuous.TestHelpers.TUnit/Eventuous.TestHelpers.TUnit.csproj @@ -0,0 +1,10 @@ + + + false + enable + + + + + + diff --git a/test/Eventuous.TestHelpers.TUnit/Logging/LoggingExtensions.cs b/test/Eventuous.TestHelpers.TUnit/Logging/LoggingExtensions.cs new file mode 100644 index 000000000..bde6fc790 --- /dev/null +++ b/test/Eventuous.TestHelpers.TUnit/Logging/LoggingExtensions.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Logging; + +namespace Eventuous.TestHelpers.TUnit.Logging; + +public static class LoggingExtensions { + public static ILoggerFactory GetLoggerFactory(LogLevel logLevel = LogLevel.Debug) + => LoggerFactory.Create( + builder => builder + .SetMinimumLevel(logLevel) + .AddFilter("Microsoft", LogLevel.Warning) + .AddFilter("Grpc", LogLevel.Warning) + .AddTUnit() + ); + + public static ILoggerFactory AddTUnit(this ILoggerFactory factory) { + factory.AddProvider(new TUnitLoggerProvider()); + + return factory; + } + + public static ILoggingBuilder AddTUnit(this ILoggingBuilder builder) => builder.AddProvider(new TUnitLoggerProvider()); + + public static ILoggingBuilder ForTests(this ILoggingBuilder builder, LogLevel logLevel = LogLevel.Debug) + => builder.AddTUnit().SetMinimumLevel(logLevel).AddFilter("Grpc", LogLevel.Warning).AddFilter("Microsoft", LogLevel.Warning); +} + +public sealed class TUnitLoggerProvider() : ILoggerProvider { + private readonly LoggerExternalScopeProvider _scopeProvider = new(); + + public ILogger CreateLogger(string categoryName) => new TUnitLog(_scopeProvider, categoryName); + + public void Dispose() { } +} diff --git a/test/Eventuous.TestHelpers.TUnit/Logging/TUnitLogger.cs b/test/Eventuous.TestHelpers.TUnit/Logging/TUnitLogger.cs new file mode 100644 index 000000000..755a0c5e4 --- /dev/null +++ b/test/Eventuous.TestHelpers.TUnit/Logging/TUnitLogger.cs @@ -0,0 +1,43 @@ +// Copyright (C) Ubiquitous AS.All rights reserved +// Licensed under the Apache License, Version 2.0. + +using Microsoft.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; + +namespace Eventuous.TestHelpers.TUnit.Logging; + +public sealed class TUnitLog(LoggerExternalScopeProvider scopeProvider) : TUnitLog(scopeProvider, typeof(T).FullName), Microsoft.Extensions.Logging.ILogger; + +public class TUnitLog : ILogger { + private readonly string? _categoryName; + private readonly LoggerExternalScopeProvider _scopeProvider; + + public static ILogger CreateLogger() => new TUnitLog(new(), ""); + + public static Microsoft.Extensions.Logging.ILogger CreateLogger() => new TUnitLog(new()); + + public TUnitLog(LoggerExternalScopeProvider scopeProvider, string? categoryName) { + _scopeProvider = scopeProvider; + _categoryName = categoryName; + } + + public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; + + public IDisposable? BeginScope(TState state) where TState : notnull => _scopeProvider.Push(state); + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { + if (TestContext.Current == null) return; + + var level = logLevel switch { + LogLevel.Trace => global::TUnit.Core.Logging.LogLevel.Trace, + LogLevel.Debug => global::TUnit.Core.Logging.LogLevel.Debug, + LogLevel.Information => global::TUnit.Core.Logging.LogLevel.Information, + LogLevel.Warning => global::TUnit.Core.Logging.LogLevel.Warning, + LogLevel.Error => global::TUnit.Core.Logging.LogLevel.Error, + LogLevel.Critical => global::TUnit.Core.Logging.LogLevel.Critical, + _ => throw new ArgumentOutOfRangeException(nameof(logLevel)) + }; + TestContext.Current.GetDefaultLogger().Log(level, state, exception, formatter); + } +} diff --git a/test/Eventuous.TestHelpers/TestEventListener.cs b/test/Eventuous.TestHelpers.TUnit/TestEventListener.cs similarity index 78% rename from test/Eventuous.TestHelpers/TestEventListener.cs rename to test/Eventuous.TestHelpers.TUnit/TestEventListener.cs index 9732199d9..a12c4370e 100644 --- a/test/Eventuous.TestHelpers/TestEventListener.cs +++ b/test/Eventuous.TestHelpers.TUnit/TestEventListener.cs @@ -1,8 +1,8 @@ using System.Diagnostics.Tracing; -namespace Eventuous.TestHelpers; +namespace Eventuous.TestHelpers.TUnit; -public sealed class TestEventListener(ITestOutputHelper outputHelper, Action? act = null, params string[] prefixes) : EventListener { +public sealed class TestEventListener(Action? act = null, params string[] prefixes) : EventListener { readonly string[] _prefixes = prefixes.Length > 0 ? prefixes : ["OpenTelemetry", "eventuous"]; readonly List _eventSources = []; @@ -23,7 +23,7 @@ protected override void OnEventSourceCreated(EventSource? eventSource) { #nullable disable protected override void OnEventWritten(EventWrittenEventArgs evt) { var message = evt.Message != null && (evt.Payload?.Count ?? 0) > 0 ? string.Format(evt.Message, evt.Payload.ToArray()) : evt.Message; - outputHelper.WriteLine($"{evt.EventSource.Name} - EventId: [{evt.EventId}], EventName: [{evt.EventName}], Message: [{message}]"); + TestContext.Current?.OutputWriter.WriteLine($"{evt.EventSource.Name} - EventId: [{evt.EventId}], EventName: [{evt.EventName}], Message: [{message}]"); act?.Invoke(evt); } #nullable enable diff --git a/test/Eventuous.TestHelpers/Eventuous.TestHelpers.csproj b/test/Eventuous.TestHelpers/Eventuous.TestHelpers.csproj index 482c8bf13..c1c0df14a 100644 --- a/test/Eventuous.TestHelpers/Eventuous.TestHelpers.csproj +++ b/test/Eventuous.TestHelpers/Eventuous.TestHelpers.csproj @@ -5,16 +5,6 @@ - - - - - - - - - - diff --git a/test/Eventuous.TestHelpers/Logging.cs b/test/Eventuous.TestHelpers/Logging.cs deleted file mode 100644 index 36e943783..000000000 --- a/test/Eventuous.TestHelpers/Logging.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace Eventuous.TestHelpers; - -public static class Logging { - public static ILoggerFactory GetLoggerFactory(ITestOutputHelper outputHelper, LogLevel logLevel = LogLevel.Debug) - => LoggerFactory.Create( - builder => builder - .SetMinimumLevel(logLevel) - .AddFilter("Microsoft", LogLevel.Warning) - .AddFilter("Grpc.Net.Client", LogLevel.Warning) - .AddXunit(outputHelper, logLevel) - ); -}