diff --git a/.github/workflows/release-preview.yml b/.github/workflows/release-preview.yml index 6835c7b..4b73b27 100644 --- a/.github/workflows/release-preview.yml +++ b/.github/workflows/release-preview.yml @@ -28,10 +28,10 @@ jobs: - name: ⚙️ Setup GIT versioning uses: dotnet/nbgv@v0.4.0 - - name: ⚙️ Setup dotnet 7.0.x + - name: ⚙️ Setup dotnet 9.0.x uses: actions/setup-dotnet@v1 with: - dotnet-version: '7.0.x' + dotnet-version: '9.0.x' - name: 🛠️ Building library in release mode run: dotnet pack -c Release -o ${GITHUB_WORKSPACE}/packages -p:ContinuousIntegrationBuild=true -p:publicrelease=true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf90c5e..f1d686a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,10 +59,10 @@ jobs: with: setAllVars: true - - name: ⚙️ Setup dotnet 7.0.x + - name: ⚙️ Setup dotnet 9.0.x uses: actions/setup-dotnet@v1 with: - dotnet-version: '7.0.x' + dotnet-version: '9.0.x' - name: 🛠️ Update changelog uses: thomaseizinger/keep-a-changelog-new-release@1.2.1 diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml index c1228c3..47f3240 100644 --- a/.github/workflows/verification.yml +++ b/.github/workflows/verification.yml @@ -33,10 +33,10 @@ jobs: with: setAllVars: true - - name: ⚙️ Setup dotnet 7.0.x + - name: ⚙️ Setup dotnet 9.0.x uses: actions/setup-dotnet@v1 with: - dotnet-version: '7.0.x' + dotnet-version: '9.0.x' - name: 🛠️ Building libraries in release mode run: dotnet build -c release -p:ContinuousIntegrationBuild=true diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a1d26d..1169292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Implement `IEventStoreManagementClient.DeleteStreamAsync` using the newly released `DeleteAllItemsByPartitionKeyStreamAsync` method in the Cosmos SDK. +- Extend `CommandContext` with the current `StreamVersion` of the stream. + ## [1.13.3] - 2024-04-21 ### Added @@ -187,4 +192,4 @@ services.AddEventStore(builder => [1.3.3]: https://github.com/atc-net/atc-cosmos-eventstore/compare/v1.2.9...v1.3.3 -[1.2.9]: https://github.com/atc-net/atc-cosmos-eventstore/compare/v1.1.3...v1.2.9 +[1.2.9]: https://github.com/atc-net/atc-cosmos-eventstore/compare/v1.1.3...v1.2.9 \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 5a094fb..15612a6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,7 +27,7 @@ - AllEnabledByDefault + Default true latest true diff --git a/sample/.editorconfig b/sample/.editorconfig deleted file mode 100644 index d6c0e20..0000000 --- a/sample/.editorconfig +++ /dev/null @@ -1,80 +0,0 @@ -# ATC coding rules - https://github.com/atc-net/atc-coding-rules -# Version: 1.0.0 -# Updated: 04-12-2020 -# Location: Sample -# Inspired by: https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-style-rule-options - -########################################## -# Code Analyzers Rules -########################################## -[*.{cs,csx,cake}] - -# AsyncFixer -# http://www.asyncfixer.com - - -# Asyncify -# https://github.com/hvanbakel/Asyncify-CSharp - - -# Meziantou -# https://www.meziantou.net/enforcing-asynchronous-code-good-practices-using-a-roslyn-analyzer.htm - -# Microsoft - Code Analysis -# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ - - -# Microsoft - FxCop -# https://github.com/dotnet/roslyn-analyzers - - -# Microsoft - Threading -# https://github.com/microsoft/vs-threading/blob/master/doc/analyzers/index.md - - -# SecurityCodeScan -# https://security-code-scan.github.io/ - - -# StyleCop -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers - - -########################################## -# Custom - Code Analyzers Rules -########################################## - -# AsyncFixer -# http://www.asyncfixer.com - - -# Asyncify -# https://github.com/hvanbakel/Asyncify-CSharp - - -# Meziantou -# https://www.meziantou.net/enforcing-asynchronous-code-good-practices-using-a-roslyn-analyzer.htm - -# Microsoft - Code Analysis -# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ - - -# Microsoft - FxCop -# https://github.com/dotnet/roslyn-analyzers - - -# Microsoft - Threading -# https://github.com/microsoft/vs-threading/blob/master/doc/analyzers/index.md - - -# SecurityCodeScan -# https://security-code-scan.github.io/ - - -# StyleCop -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers - - -########################################## -# Custom - Code Analyzers Rules -########################################## \ No newline at end of file diff --git a/sample/GettingStarted/Commands/CreateCommands.cs b/sample/GettingStarted/Commands/CreateCommands.cs new file mode 100644 index 0000000..672e3fb --- /dev/null +++ b/sample/GettingStarted/Commands/CreateCommands.cs @@ -0,0 +1,31 @@ +using Atc.Cosmos.EventStore.Cqrs; + +namespace GettingStarted; + +public record CreateCommand(string Id, string Name, string Address) + : CommandBase(new SampleEventStreamId(Id)); + +public class CreateCommandHandler : + ICommandHandler, + IConsumeEvent +{ + private bool created; + + public void Consume(CreateCommand evt, EventMetadata metadata) + { + this.created = true; + } + + public ValueTask ExecuteAsync( + CreateCommand command, + ICommandContext context, + CancellationToken cancellationToken) + { + if (!created) + { + context.AddEvent(new AddedEvent(command.Name, command.Address)); + } + + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/sample/GettingStarted/Commands/DeleteCommand.cs b/sample/GettingStarted/Commands/DeleteCommand.cs new file mode 100644 index 0000000..b812777 --- /dev/null +++ b/sample/GettingStarted/Commands/DeleteCommand.cs @@ -0,0 +1,44 @@ +using Atc.Cosmos.EventStore.Cqrs; + +namespace GettingStarted; + +public record DeleteCommand(string Id, string Reason) + : CommandBase(new SampleEventStreamId(Id)); + +public class DeleteCommandHandler : + ICommandHandler, + IConsumeEvent, + IConsumeEvent +{ + private bool created; + private bool deleted; + + public void Consume(AddedEvent evt, EventMetadata metadata) + { + this.created = true; + } + + public void Consume(DeletedEvent evt, EventMetadata metadata) + { + this.deleted = true; + } + + public ValueTask ExecuteAsync( + DeleteCommand command, + ICommandContext context, + CancellationToken cancellationToken) + { + if (!created) + { + throw new InvalidOperationException("Cannot delete non-existing entity."); + } + + if (deleted) + { + throw new InvalidOperationException("Already deleted."); + } + + context.AddEvent(new DeletedEvent(command.Reason)); + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/sample/GettingStarted/Commands/UpdateNameCommands.cs b/sample/GettingStarted/Commands/UpdateNameCommands.cs new file mode 100644 index 0000000..67feac3 --- /dev/null +++ b/sample/GettingStarted/Commands/UpdateNameCommands.cs @@ -0,0 +1,44 @@ +using Atc.Cosmos.EventStore.Cqrs; + +namespace GettingStarted; + +public record UpdateNameCommand(string Id, string Name) + : CommandBase(new SampleEventStreamId(Id)); + +public class UpdateNameCommandHandler : + ICommandHandler, + IConsumeEvent, + IConsumeEvent +{ + private bool created; + private string? currentName; + + public void Consume(AddedEvent evt, EventMetadata metadata) + { + created = true; + currentName = evt.Name; + } + + public void Consume(NameChangedEvent evt, EventMetadata metadata) + { + currentName = evt.NewName; + } + + public ValueTask ExecuteAsync( + UpdateNameCommand command, + ICommandContext context, + CancellationToken cancellationToken) + { + if (!created) + { + throw new InvalidOperationException("Cannot change name on non-existing entity."); + } + + if (currentName != command.Name) + { + context.AddEvent(new NameChangedEvent(currentName!, command.Name)); + } + + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/sample/GettingStarted/ConsoleHostedService.cs b/sample/GettingStarted/ConsoleHostedService.cs new file mode 100644 index 0000000..0341bae --- /dev/null +++ b/sample/GettingStarted/ConsoleHostedService.cs @@ -0,0 +1,44 @@ +using Atc.Cosmos.EventStore.Cqrs; +using Microsoft.Extensions.Hosting; + +namespace GettingStarted; + +public class ConsoleHostedService(ICommandProcessorFactory commandProcessorFactory) : IHostedService +{ + public async Task StartAsync(CancellationToken cancellationToken) + { + var id = Guid.NewGuid().ToString("N"); + + await commandProcessorFactory + .Create() + .ExecuteAsync( + new CreateCommand(id, "First", "Address 1"), + cancellationToken); + + await commandProcessorFactory + .Create() + .ExecuteAsync( + new UpdateNameCommand(id, "Second"), + cancellationToken); + + await commandProcessorFactory + .Create() + .ExecuteAsync( + new UpdateNameCommand(id, "Third"), + cancellationToken); + + await commandProcessorFactory + .Create() + .ExecuteAsync( + new DeleteCommand(id, "Deleted"), + cancellationToken); + + await commandProcessorFactory + .Create() + .ExecuteAsync( + new DeleteCommand(id, "Deleted"), + cancellationToken); + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; +} \ No newline at end of file diff --git a/sample/GettingStarted/Events/AddedEvent.cs b/sample/GettingStarted/Events/AddedEvent.cs new file mode 100644 index 0000000..0f7fdc4 --- /dev/null +++ b/sample/GettingStarted/Events/AddedEvent.cs @@ -0,0 +1,6 @@ +using Atc.Cosmos.EventStore.Cqrs; + +namespace GettingStarted; + +[StreamEvent("added-event:v1")] +public record AddedEvent(string Name, string Address); \ No newline at end of file diff --git a/sample/GettingStarted/Events/AddressChangedEvent.cs b/sample/GettingStarted/Events/AddressChangedEvent.cs new file mode 100644 index 0000000..3840e1f --- /dev/null +++ b/sample/GettingStarted/Events/AddressChangedEvent.cs @@ -0,0 +1,6 @@ +using Atc.Cosmos.EventStore.Cqrs; + +namespace GettingStarted; + +[StreamEvent("address-changed-event:v1")] +public record AddressChangedEvent(string OldAddress, string NewAddress); \ No newline at end of file diff --git a/sample/GettingStarted/Events/DeletedEvent.cs b/sample/GettingStarted/Events/DeletedEvent.cs new file mode 100644 index 0000000..f765623 --- /dev/null +++ b/sample/GettingStarted/Events/DeletedEvent.cs @@ -0,0 +1,6 @@ +using Atc.Cosmos.EventStore.Cqrs; + +namespace GettingStarted; + +[StreamEvent("deleted-event:v1")] +public record DeletedEvent(string Reason); \ No newline at end of file diff --git a/sample/GettingStarted/Events/NameChangedEvent.cs b/sample/GettingStarted/Events/NameChangedEvent.cs new file mode 100644 index 0000000..2ecbddb --- /dev/null +++ b/sample/GettingStarted/Events/NameChangedEvent.cs @@ -0,0 +1,6 @@ +using Atc.Cosmos.EventStore.Cqrs; + +namespace GettingStarted; + +[StreamEvent("name-changed-event:v1")] +public record NameChangedEvent(string OldName, string NewName); \ No newline at end of file diff --git a/sample/GettingStarted/GettingStarted.csproj b/sample/GettingStarted/GettingStarted.csproj new file mode 100644 index 0000000..845cc80 --- /dev/null +++ b/sample/GettingStarted/GettingStarted.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + enable + enable + latest + + + + + + + + + diff --git a/sample/GettingStarted/Program.cs b/sample/GettingStarted/Program.cs new file mode 100644 index 0000000..6e7b4d8 --- /dev/null +++ b/sample/GettingStarted/Program.cs @@ -0,0 +1,62 @@ +using Atc.Cosmos; +using Atc.Cosmos.EventStore; +using GettingStarted; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Atc.Cosmos.Internal; +using GettingStarted.Storage; +using Microsoft.Extensions.Options; + +void ConfigureServices(IServiceCollection services) +{ + services.ConfigureOptions(); + services.ConfigureCosmos(builder => + { + builder.AddContainer(ContainerInitializer.Name); + builder.UseHostedService(); + }); + + services.ConfigureOptions(); + services.AddEventStore( + builder => + { + builder.UseCosmosDb(); + builder.UseEvents(catalogBuilder => catalogBuilder.FromAssembly()); + builder.UseCQRS( + c => + { + c.AddInitialization( + 4000, + serviceProvider => serviceProvider + .GetRequiredService() + .InitializeAsync(CancellationToken.None)); + + c.AddCommandsFromAssembly(); + c.AddProjectionJob(nameof(SampleProjection)); + }); + }); + + services.AddHostedService(); +} + +await Host.CreateDefaultBuilder() + .ConfigureServices(ConfigureServices) + .RunConsoleAsync(); + +public class ConfigureCosmosOptions : IConfigureOptions +{ + public void Configure(CosmosOptions options) + { + options.UseCosmosEmulator(); + options.DatabaseName = "CQRS"; + } +} + +public class ConfigureEventStoreOptions : IConfigureOptions +{ + public void Configure(EventStoreClientOptions options) + { + options.UseCosmosEmulator(); + options.EventStoreDatabaseId = "CQRS"; + } +} \ No newline at end of file diff --git a/sample/GettingStarted/Projections/ContainerInitializer.cs b/sample/GettingStarted/Projections/ContainerInitializer.cs new file mode 100644 index 0000000..d68cf43 --- /dev/null +++ b/sample/GettingStarted/Projections/ContainerInitializer.cs @@ -0,0 +1,25 @@ +using Atc.Cosmos; +using Microsoft.Azure.Cosmos; + +namespace GettingStarted.Storage; + +public class ContainerInitializer : ICosmosContainerInitializer +{ + public const string Name = "read-models"; + + public Task InitializeAsync(Database database, CancellationToken cancellationToken) + { + var options = new ContainerProperties + { + IndexingPolicy = new IndexingPolicy + { + Automatic = true, + IndexingMode = IndexingMode.Consistent, + }, + PartitionKeyPath = "/pk", + Id = Name, + }; + + return database.CreateContainerIfNotExistsAsync(options, cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/sample/GettingStarted/Projections/SampleProjection.cs b/sample/GettingStarted/Projections/SampleProjection.cs new file mode 100644 index 0000000..789bbd0 --- /dev/null +++ b/sample/GettingStarted/Projections/SampleProjection.cs @@ -0,0 +1,66 @@ +using Atc.Cosmos; +using Atc.Cosmos.EventStore.Cqrs; + +namespace GettingStarted.Storage; + +[ProjectionFilter(SampleEventStreamId.FilterIncludeAllEvents)] +public class SampleProjection( + ICosmosReader reader, + ICosmosWriter writer) : + IProjection, + IConsumeEvent, + IConsumeEvent, + IConsumeEvent, + IConsumeEvent +{ + private SampleReadModel view = null!; + private bool deleted = false; + + public Task FailedAsync( + Exception exception, + CancellationToken cancellationToken) => + Task.FromResult(ProjectionAction.Continue); + + public async Task InitializeAsync( + EventStreamId id, + CancellationToken cancellationToken) + { + var streamId = new SampleEventStreamId(id); + view = await reader.FindAsync( + streamId.Id, + streamId.Id, + cancellationToken) ?? + new SampleReadModel + { + Id = streamId.Id, + }; + } + + public Task CompleteAsync( + CancellationToken cancellationToken) => + deleted + ? writer.TryDeleteAsync(view!.Id, view!.PartitionKey, cancellationToken) + : writer.WriteAsync(view, cancellationToken); + + public void Consume(AddedEvent evt, EventMetadata metadata) + { + view.Name = evt.Name; + view.Address = evt.Address; + deleted = false; + } + + public void Consume(NameChangedEvent evt, EventMetadata metadata) + { + view.Name = evt.NewName; + } + + public void Consume(AddressChangedEvent evt, EventMetadata metadata) + { + view.Address = evt.NewAddress; + } + + public void Consume(DeletedEvent evt, EventMetadata metadata) + { + deleted = true; + } +} \ No newline at end of file diff --git a/sample/GettingStarted/Projections/SampleReadModel.cs b/sample/GettingStarted/Projections/SampleReadModel.cs new file mode 100644 index 0000000..1d8dee4 --- /dev/null +++ b/sample/GettingStarted/Projections/SampleReadModel.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; +using Atc.Cosmos; + +namespace GettingStarted.Storage; + +public class SampleReadModel : CosmosResource +{ + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("pk")] + public string PartitionKey { get; set; } + + public string Name { get; set; } + + public string Address { get; set; } + + protected override string GetDocumentId() => Id; + + protected override string GetPartitionKey() => PartitionKey; +} \ No newline at end of file diff --git a/sample/GettingStarted/SampleEventStreamId.cs b/sample/GettingStarted/SampleEventStreamId.cs new file mode 100644 index 0000000..0ffa25a --- /dev/null +++ b/sample/GettingStarted/SampleEventStreamId.cs @@ -0,0 +1,32 @@ +using Atc.Cosmos.EventStore.Cqrs; + +namespace GettingStarted; + +public sealed class SampleEventStreamId : EventStreamId, IEquatable +{ + private const string TypeName = "sample"; + public const string FilterIncludeAllEvents = TypeName + ".*"; + + public SampleEventStreamId(string id) + : base(TypeName, id) + { + Id = id; + } + + public SampleEventStreamId(EventStreamId id) + : base(id.Parts.ToArray()) + { + Id = id.Parts[1]; + } + + public string Id { get; } + + public override bool Equals(object? obj) + => Equals(obj as SampleEventStreamId); + + public bool Equals(SampleEventStreamId? other) + => other != null && Value == other.Value; + + public override int GetHashCode() + => HashCode.Combine(Value); +} \ No newline at end of file diff --git a/sample/directory.build.props b/sample/directory.build.props deleted file mode 100644 index 6f820a0..0000000 --- a/sample/directory.build.props +++ /dev/null @@ -1,12 +0,0 @@ - - - - - false - - - - - - - \ No newline at end of file diff --git a/sample/directory.build.targets b/sample/directory.build.targets deleted file mode 100644 index 467d7af..0000000 --- a/sample/directory.build.targets +++ /dev/null @@ -1,23 +0,0 @@ - - - - - latest - $(NoWarn);1591 - - - true - bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml - - - full - true - - - - - - \ No newline at end of file diff --git a/src/Atc.Cosmos.EventStore.Cqrs/Commands/CommandContext.cs b/src/Atc.Cosmos.EventStore.Cqrs/Commands/CommandContext.cs index 2f39a51..24dc2f0 100644 --- a/src/Atc.Cosmos.EventStore.Cqrs/Commands/CommandContext.cs +++ b/src/Atc.Cosmos.EventStore.Cqrs/Commands/CommandContext.cs @@ -5,10 +5,17 @@ namespace Atc.Cosmos.EventStore.Cqrs.Commands; internal class CommandContext : ICommandContext, ICommandContextInspector { + public StreamVersion StreamVersion { get; } + public const int EventLimit = 10; private readonly List appliedEvents = new(); + public CommandContext(StreamVersion streamVersion) + { + StreamVersion = streamVersion; + } + public IReadOnlyCollection Events => appliedEvents; diff --git a/src/Atc.Cosmos.EventStore.Cqrs/Commands/CommandProcessor.cs b/src/Atc.Cosmos.EventStore.Cqrs/Commands/CommandProcessor.cs index 82afbaf..e5405ff 100644 --- a/src/Atc.Cosmos.EventStore.Cqrs/Commands/CommandProcessor.cs +++ b/src/Atc.Cosmos.EventStore.Cqrs/Commands/CommandProcessor.cs @@ -60,7 +60,7 @@ private async ValueTask SafeExecuteAsync( .ConfigureAwait(false); // Execute command on aggregate. - var context = new CommandContext(); + var context = new CommandContext(state.Version); await handler .ExecuteAsync(command, context, cancellationToken) .ConfigureAwait(false); diff --git a/src/Atc.Cosmos.EventStore.Cqrs/ICommandContext.cs b/src/Atc.Cosmos.EventStore.Cqrs/ICommandContext.cs index 334c92a..181802a 100644 --- a/src/Atc.Cosmos.EventStore.Cqrs/ICommandContext.cs +++ b/src/Atc.Cosmos.EventStore.Cqrs/ICommandContext.cs @@ -2,6 +2,8 @@ namespace Atc.Cosmos.EventStore.Cqrs; public interface ICommandContext { + StreamVersion StreamVersion { get; } + void AddEvent(object evt); object? ResponseObject { get; set; } diff --git a/src/Atc.Cosmos.EventStore.Cqrs/Testing/CommandHandlerTester.cs b/src/Atc.Cosmos.EventStore.Cqrs/Testing/CommandHandlerTester.cs index f9f6c25..426be53 100644 --- a/src/Atc.Cosmos.EventStore.Cqrs/Testing/CommandHandlerTester.cs +++ b/src/Atc.Cosmos.EventStore.Cqrs/Testing/CommandHandlerTester.cs @@ -125,7 +125,7 @@ await handlerMetadata .ConfigureAwait(false); } - var context = new CommandContext(); + var context = new CommandContext(version); await handler .ExecuteAsync(command!, context, cancellationToken) diff --git a/src/Atc.Cosmos.EventStore.Cqrs/Testing/ICommandContextInspector.cs b/src/Atc.Cosmos.EventStore.Cqrs/Testing/ICommandContextInspector.cs index a54064b..43b609a 100644 --- a/src/Atc.Cosmos.EventStore.Cqrs/Testing/ICommandContextInspector.cs +++ b/src/Atc.Cosmos.EventStore.Cqrs/Testing/ICommandContextInspector.cs @@ -2,6 +2,8 @@ public interface ICommandContextInspector { + StreamVersion StreamVersion { get; } + IReadOnlyCollection Events { get; } object? ResponseObject { get; } diff --git a/src/Atc.Cosmos.EventStore/Atc.Cosmos.EventStore.csproj b/src/Atc.Cosmos.EventStore/Atc.Cosmos.EventStore.csproj index a60f81d..3dbece5 100644 --- a/src/Atc.Cosmos.EventStore/Atc.Cosmos.EventStore.csproj +++ b/src/Atc.Cosmos.EventStore/Atc.Cosmos.EventStore.csproj @@ -9,6 +9,8 @@ true Portable $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + true @@ -16,14 +18,14 @@ - - - - - - - - + + + + + + + + diff --git a/src/Atc.Cosmos.EventStore/Cosmos/CosmosDeleter.cs b/src/Atc.Cosmos.EventStore/Cosmos/CosmosDeleter.cs new file mode 100644 index 0000000..a424e2d --- /dev/null +++ b/src/Atc.Cosmos.EventStore/Cosmos/CosmosDeleter.cs @@ -0,0 +1,25 @@ +using Atc.Cosmos.EventStore.Streams; +using Microsoft.Azure.Cosmos; + +namespace Atc.Cosmos.EventStore.Cosmos; + +internal class CosmosDeleter : IStreamDeleter +{ + private readonly IEventStoreContainerProvider containerProvider; + + public CosmosDeleter(IEventStoreContainerProvider containerProvider) + { + this.containerProvider = containerProvider; + } + + public async Task DeleteAsync( + StreamId streamId, + CancellationToken cancellationToken) + { + var pk = new PartitionKey(streamId.Value); + var container = containerProvider.GetStreamContainer(); + var response = await container.DeleteAllItemsByPartitionKeyStreamAsync(pk, null, cancellationToken); + + response.EnsureSuccessStatusCode(); + } +} \ No newline at end of file diff --git a/src/Atc.Cosmos.EventStore/DependencyInjection/EventStoreOptionsBuilder.cs b/src/Atc.Cosmos.EventStore/DependencyInjection/EventStoreOptionsBuilder.cs index dee8590..e157232 100644 --- a/src/Atc.Cosmos.EventStore/DependencyInjection/EventStoreOptionsBuilder.cs +++ b/src/Atc.Cosmos.EventStore/DependencyInjection/EventStoreOptionsBuilder.cs @@ -33,6 +33,7 @@ public EventStoreOptionsBuilder UseCosmosDb( Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); + Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); @@ -74,6 +75,7 @@ internal EventStoreOptionsBuilder UseInMemoryDb() Services.TryAddSingleton(s => s.GetRequiredService()); Services.TryAddSingleton(s => s.GetRequiredService()); Services.TryAddSingleton(s => s.GetRequiredService()); + Services.TryAddSingleton(s => s.GetRequiredService()); Services.TryAddSingleton(s => s.GetRequiredService()); Services.TryAddSingleton(s => s.GetRequiredService()); Services.TryAddSingleton(s => s.GetRequiredService()); diff --git a/src/Atc.Cosmos.EventStore/EventStoreManagementClient.cs b/src/Atc.Cosmos.EventStore/EventStoreManagementClient.cs index 7c59610..f6dcc8a 100644 --- a/src/Atc.Cosmos.EventStore/EventStoreManagementClient.cs +++ b/src/Atc.Cosmos.EventStore/EventStoreManagementClient.cs @@ -1,11 +1,20 @@ +using Atc.Cosmos.EventStore.Streams; + namespace Atc.Cosmos.EventStore; internal class EventStoreManagementClient : IEventStoreManagementClient { + private readonly IStreamDeleter streamDeleter; + + public EventStoreManagementClient(IStreamDeleter streamDeleter) + { + this.streamDeleter = streamDeleter; + } + public Task DeleteStreamAsync( StreamId streamId, CancellationToken cancellationToken = default) - => throw new NotImplementedException(); + => streamDeleter.DeleteAsync(streamId, cancellationToken); public Task PurgeStreamAsync( StreamId streamId, diff --git a/src/Atc.Cosmos.EventStore/IEventStoreManagementClient.cs b/src/Atc.Cosmos.EventStore/IEventStoreManagementClient.cs index d615184..8b4d022 100644 --- a/src/Atc.Cosmos.EventStore/IEventStoreManagementClient.cs +++ b/src/Atc.Cosmos.EventStore/IEventStoreManagementClient.cs @@ -39,7 +39,7 @@ Task PurgeStreamAsync( CancellationToken cancellationToken = default); /// - /// Deletes an entire stream and it's index. + /// Deletes an entire stream. /// /// Attempting to write to a deleted stream will create a new empty stream. /// Id of the event stream to delete. diff --git a/src/Atc.Cosmos.EventStore/InMemory/InMemoryStore.cs b/src/Atc.Cosmos.EventStore/InMemory/InMemoryStore.cs index 9ea0de1..4f6376a 100644 --- a/src/Atc.Cosmos.EventStore/InMemory/InMemoryStore.cs +++ b/src/Atc.Cosmos.EventStore/InMemory/InMemoryStore.cs @@ -9,6 +9,7 @@ internal class InMemoryStore : IStreamMetadataReader, IStreamIterator, IStreamBatchWriter, + IStreamDeleter, IStreamSubscriptionFactory, IStreamSubscriptionRemover, IStreamIndexReader, @@ -69,6 +70,9 @@ Task IStreamBatchWriter.WriteAsync( CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task DeleteAsync(StreamId streamId, CancellationToken cancellationToken) + => throw new NotImplementedException(); + public Task WriteAsync( string name, StreamId streamId, diff --git a/src/Atc.Cosmos.EventStore/Streams/IStreamDeleter.cs b/src/Atc.Cosmos.EventStore/Streams/IStreamDeleter.cs new file mode 100644 index 0000000..6e427c9 --- /dev/null +++ b/src/Atc.Cosmos.EventStore/Streams/IStreamDeleter.cs @@ -0,0 +1,8 @@ +namespace Atc.Cosmos.EventStore.Streams; + +internal interface IStreamDeleter +{ + Task DeleteAsync( + StreamId streamId, + CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/test/Atc.Cosmos.EventStore.Cqrs.Tests/Atc.Cosmos.EventStore.Cqrs.Tests.csproj b/test/Atc.Cosmos.EventStore.Cqrs.Tests/Atc.Cosmos.EventStore.Cqrs.Tests.csproj index 344165f..e77b84c 100644 --- a/test/Atc.Cosmos.EventStore.Cqrs.Tests/Atc.Cosmos.EventStore.Cqrs.Tests.csproj +++ b/test/Atc.Cosmos.EventStore.Cqrs.Tests/Atc.Cosmos.EventStore.Cqrs.Tests.csproj @@ -1,20 +1,20 @@ - net7.0 + net9.0 false - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Atc.Cosmos.EventStore.Cqrs.Tests/Commands/CommandProcessorTests.cs b/test/Atc.Cosmos.EventStore.Cqrs.Tests/Commands/CommandProcessorTests.cs new file mode 100644 index 0000000..5c835d3 --- /dev/null +++ b/test/Atc.Cosmos.EventStore.Cqrs.Tests/Commands/CommandProcessorTests.cs @@ -0,0 +1,180 @@ +using System.Collections.ObjectModel; +using Atc.Cosmos.EventStore.Cqrs.Commands; +using Atc.Cosmos.EventStore.Cqrs.Tests.Mocks; +using Atc.Test; +using AutoFixture.Xunit2; +using FluentAssertions; +using NSubstitute; +using Xunit; + +namespace Atc.Cosmos.EventStore.Cqrs.Tests.Commands; + +public class CommandProcessorTests +{ + [Theory, AutoNSubstituteData] + internal async Task Should_Exeute_State_Projector( + [Frozen] ICommandHandlerFactory commandHandlerFactory, + [Frozen] IStateProjector stateProjector, + CommandProcessor sut, + MockCommand command, + ICommandHandler handler, + Atc.Cosmos.EventStore.Cqrs.Commands.StreamState streamState, + CancellationToken cancellationToken) + { + commandHandlerFactory.Create().Returns(handler); + stateProjector.ProjectAsync(command, handler, cancellationToken).Returns(streamState); + + await sut.ExecuteAsync(command, cancellationToken); + + await stateProjector.Received(1).ProjectAsync(command, handler, cancellationToken); + } + + [Theory, AutoNSubstituteData] + internal async Task Should_Execute_Command( + [Frozen] ICommandHandlerFactory commandHandlerFactory, + [Frozen] IStateProjector stateProjector, + CommandProcessor sut, + MockCommand command, + ICommandHandler handler, + Atc.Cosmos.EventStore.Cqrs.Commands.StreamState streamState, + CancellationToken cancellationToken) + { + commandHandlerFactory.Create().Returns(handler); + stateProjector.ProjectAsync(command, handler, cancellationToken).Returns(streamState); + + await sut.ExecuteAsync(command, cancellationToken); + + await handler.Received(1).ExecuteAsync(command, Arg.Any(), cancellationToken); + } + + [Theory, AutoNSubstituteData] + internal async Task Should_Set_Command_Context_StreamVersion( + [Frozen] ICommandHandlerFactory commandHandlerFactory, + [Frozen] IStateProjector stateProjector, + CommandProcessor sut, + MockCommand command, + ICommandHandler handler, + Atc.Cosmos.EventStore.Cqrs.Commands.StreamState streamState, + CancellationToken cancellationToken) + { + commandHandlerFactory.Create().Returns(handler); + stateProjector.ProjectAsync(command, handler, cancellationToken).Returns(streamState); + + await sut.ExecuteAsync(command, cancellationToken); + + var commandContext = handler.ReceivedCallWithArgument(); + commandContext.StreamVersion.Should().Be(streamState.Version); + } + + [Theory, AutoNSubstituteData] + internal async Task Should_Return_NotModified_When_Command_Emits_No_Events( + [Frozen] ICommandHandlerFactory commandHandlerFactory, + [Frozen] IStateProjector stateProjector, + CommandProcessor sut, + MockCommand command, + MockCommandHandler handler, + Atc.Cosmos.EventStore.Cqrs.Commands.StreamState streamState, + CancellationToken cancellationToken) + { + commandHandlerFactory.Create().Returns(handler); + stateProjector.ProjectAsync(command, handler, cancellationToken).Returns(streamState); + + var result = await sut.ExecuteAsync(command, cancellationToken); + + result.Result.Should().Be(ResultType.NotModified); + } + + [Theory, AutoNSubstituteData] + internal async Task Should_Write_ResposeObject_To_CommandResult_When_Command_Emits_No_Events( + [Frozen] ICommandHandlerFactory commandHandlerFactory, + [Frozen] IStateProjector stateProjector, + CommandProcessor sut, + MockCommand command, + MockCommandHandler handler, + object responseObject, + Atc.Cosmos.EventStore.Cqrs.Commands.StreamState streamState, + CancellationToken cancellationToken) + { + commandHandlerFactory.Create().Returns(handler); + stateProjector.ProjectAsync(command, handler, cancellationToken).Returns(streamState); + handler.ResponseObject = responseObject; + + var result = await sut.ExecuteAsync(command, cancellationToken); + + result.Response.Should().Be(responseObject); + } + + [Theory, AutoNSubstituteData] + internal async Task Should_Call_StateWriter_With_Events__When_Command_Emits_Events( + [Frozen] ICommandHandlerFactory commandHandlerFactory, + [Frozen] IStateProjector stateProjector, + [Frozen] IStateWriter stateWriter, + CommandProcessor sut, + MockCommand command, + CommandResult commandResult, + MockCommandHandler handler, + MockEvent[] events, + Atc.Cosmos.EventStore.Cqrs.Commands.StreamState streamState, + CancellationToken cancellationToken) + { + commandHandlerFactory.Create().Returns(handler); + stateProjector.ProjectAsync(command, handler, cancellationToken).Returns(streamState); + stateWriter.WriteEventAsync(command, events, cancellationToken).ReturnsForAnyArgs(commandResult); + handler.AddEventsToEmit(events); + + await sut.ExecuteAsync(command, cancellationToken); + + await stateWriter.Received(1).WriteEventAsync(command, Arg.Any>(), cancellationToken); + var writtenEvents = stateWriter.ReceivedCallWithArgument>(); + writtenEvents.Should().HaveSameCount(events); + writtenEvents.AsEnumerable().Should().BeEquivalentTo(events); + } + + [Theory, AutoNSubstituteData] + internal async Task Should_Return_Changed_When_Command_Emits_Events( + [Frozen] ICommandHandlerFactory commandHandlerFactory, + [Frozen] IStateProjector stateProjector, + [Frozen] IStateWriter stateWriter, + CommandProcessor sut, + MockCommand command, + MockCommandHandler handler, + CommandResult commandResult, + MockEvent[] events, + Atc.Cosmos.EventStore.Cqrs.Commands.StreamState streamState, + CancellationToken cancellationToken) + { + commandHandlerFactory.Create().Returns(handler); + stateProjector.ProjectAsync(command, handler, cancellationToken).Returns(streamState); + stateWriter.WriteEventAsync(command, events, cancellationToken).ReturnsForAnyArgs(commandResult); + handler.AddEventsToEmit(events); + + var result = await sut.ExecuteAsync(command, cancellationToken); + + result.Result.Should().Be(ResultType.Changed); + } + + [Theory, AutoNSubstituteData] + internal async Task Should_Write_ResposeObject_To_CommandResult_When_Command_Emits_Events( + [Frozen] ICommandHandlerFactory commandHandlerFactory, + [Frozen] IStateProjector stateProjector, + [Frozen] IStateWriter stateWriter, + CommandProcessor sut, + MockCommand command, + MockCommandHandler handler, + CommandResult commandResult, + MockEvent[] events, + object responseObject, + Atc.Cosmos.EventStore.Cqrs.Commands.StreamState streamState, + CancellationToken cancellationToken) + { + commandHandlerFactory.Create().Returns(handler); + stateProjector.ProjectAsync(command, handler, cancellationToken).Returns(streamState); + stateWriter.WriteEventAsync(command, events, cancellationToken).ReturnsForAnyArgs(commandResult); + handler.AddEventsToEmit(events); + handler.ResponseObject = responseObject; + + var result = await sut.ExecuteAsync(command, cancellationToken); + + result.Response.Should().Be(responseObject); + } +} \ No newline at end of file diff --git a/test/Atc.Cosmos.EventStore.Cqrs.Tests/Mocks/MockCommandHandler.cs b/test/Atc.Cosmos.EventStore.Cqrs.Tests/Mocks/MockCommandHandler.cs new file mode 100644 index 0000000..e9405c6 --- /dev/null +++ b/test/Atc.Cosmos.EventStore.Cqrs.Tests/Mocks/MockCommandHandler.cs @@ -0,0 +1,28 @@ +namespace Atc.Cosmos.EventStore.Cqrs.Tests.Mocks; + +public class MockCommandHandler : ICommandHandler +{ + private List events = new(); + + public object ResponseObject { get; set; } = null; + + public void AddEventsToEmit(params IEvent[] eventsToEmit) + { + events = events.Concat(eventsToEmit).ToList(); + } + + public ValueTask ExecuteAsync( + MockCommand command, + ICommandContext context, + CancellationToken cancellationToken) + { + foreach (var evt in events) + { + context.AddEvent(evt); + } + + context.ResponseObject = ResponseObject; + + return default; + } +} \ No newline at end of file diff --git a/test/Atc.Cosmos.EventStore.IntegrationTests/Atc.Cosmos.EventStore.IntegrationTests.csproj b/test/Atc.Cosmos.EventStore.IntegrationTests/Atc.Cosmos.EventStore.IntegrationTests.csproj index 4dbcbbb..58afc86 100644 --- a/test/Atc.Cosmos.EventStore.IntegrationTests/Atc.Cosmos.EventStore.IntegrationTests.csproj +++ b/test/Atc.Cosmos.EventStore.IntegrationTests/Atc.Cosmos.EventStore.IntegrationTests.csproj @@ -1,20 +1,20 @@ - net7.0 + net9.0 false - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Atc.Cosmos.EventStore.Tests/Atc.Cosmos.EventStore.Tests.csproj b/test/Atc.Cosmos.EventStore.Tests/Atc.Cosmos.EventStore.Tests.csproj index 0ffa593..5f3743f 100644 --- a/test/Atc.Cosmos.EventStore.Tests/Atc.Cosmos.EventStore.Tests.csproj +++ b/test/Atc.Cosmos.EventStore.Tests/Atc.Cosmos.EventStore.Tests.csproj @@ -1,20 +1,20 @@ - net7.0 + net9.0 false - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Atc.Cosmos.EventStore.Tests/Converters/EventDataConverterPipelineBuilderTests.cs b/test/Atc.Cosmos.EventStore.Tests/Converters/EventDataConverterPipelineBuilderTests.cs index a66c37e..2e24e07 100644 --- a/test/Atc.Cosmos.EventStore.Tests/Converters/EventDataConverterPipelineBuilderTests.cs +++ b/test/Atc.Cosmos.EventStore.Tests/Converters/EventDataConverterPipelineBuilderTests.cs @@ -14,7 +14,6 @@ public class EventDataConverterPipelineBuilderTests [Theory, AutoNSubstituteData] internal void Should_Call_All_Converters_InReverseOrder( IEventMetadata metadata, - JsonSerializerOptions options, FakeEventDataConverter[] converters, FakeEventDataConverter converter, EventDataConverterPipelineBuilder sut) @@ -22,7 +21,7 @@ internal void Should_Call_All_Converters_InReverseOrder( .AddConverter(converter) .AddConverters(converters) .Build() - .Convert(metadata, doc.RootElement, options) + .Convert(metadata, doc.RootElement, new JsonSerializerOptions()) .Should() .Be(string.Join(string.Empty, new[] { converter }.Concat(converters).Select(c => c.Val))); } diff --git a/test/Atc.Cosmos.EventStore.Tests/Converters/FaultedEventDataConverterTests.cs b/test/Atc.Cosmos.EventStore.Tests/Converters/FaultedEventDataConverterTests.cs index 274b85f..937c431 100644 --- a/test/Atc.Cosmos.EventStore.Tests/Converters/FaultedEventDataConverterTests.cs +++ b/test/Atc.Cosmos.EventStore.Tests/Converters/FaultedEventDataConverterTests.cs @@ -14,14 +14,13 @@ public class FaultedEventDataConverterTests [Theory, AutoNSubstituteData] internal void Should_Return_Converted_Value( IEventMetadata metadata, - JsonSerializerOptions options, string expected, FaultedEventDataConverter sut) => sut .Convert( metadata, doc.RootElement, - options, + new JsonSerializerOptions(), () => expected) .Should() .Be(expected); @@ -29,14 +28,13 @@ internal void Should_Return_Converted_Value( [Theory, AutoNSubstituteData] internal void Should_Return_FaultedEvent_When_Exception_IsThrown( IEventMetadata metadata, - JsonSerializerOptions options, KeyNotFoundException exception, FaultedEventDataConverter sut) => sut .Convert( metadata, doc.RootElement, - options, + new JsonSerializerOptions(), () => throw exception) .Should() .BeEquivalentTo( diff --git a/test/Atc.Cosmos.EventStore.Tests/Converters/NamedEventConverterTests.cs b/test/Atc.Cosmos.EventStore.Tests/Converters/NamedEventConverterTests.cs index 913e4ff..a731f7e 100644 --- a/test/Atc.Cosmos.EventStore.Tests/Converters/NamedEventConverterTests.cs +++ b/test/Atc.Cosmos.EventStore.Tests/Converters/NamedEventConverterTests.cs @@ -18,7 +18,6 @@ public class NamedEventConverterTests internal void Should_Return_Value_FromNext_When_TypeName_IsNotFound( [Frozen] IEventTypeProvider typeProvider, IEventMetadata metadata, - JsonSerializerOptions options, string expected, NamedEventConverter sut) { @@ -30,7 +29,7 @@ internal void Should_Return_Value_FromNext_When_TypeName_IsNotFound( .Convert( metadata, doc.RootElement, - options, + new JsonSerializerOptions(), () => expected) .Should() .Be(expected); diff --git a/test/Atc.Cosmos.EventStore.Tests/Converters/UnknownEventDataConverterTests.cs b/test/Atc.Cosmos.EventStore.Tests/Converters/UnknownEventDataConverterTests.cs index 74529be..4651141 100644 --- a/test/Atc.Cosmos.EventStore.Tests/Converters/UnknownEventDataConverterTests.cs +++ b/test/Atc.Cosmos.EventStore.Tests/Converters/UnknownEventDataConverterTests.cs @@ -14,14 +14,13 @@ public class UnknownEventDataConverterTests [Theory, AutoNSubstituteData] internal void Should_Return_Converted_Value_Id_NotNull( IEventMetadata metadata, - JsonSerializerOptions options, string expected, UnknownEventDataConverter sut) => sut .Convert( metadata, doc.RootElement, - options, + new JsonSerializerOptions(), () => expected) .Should() .Be(expected); @@ -29,13 +28,12 @@ internal void Should_Return_Converted_Value_Id_NotNull( [Theory, AutoNSubstituteData] internal void Should_Return_UnknownEvent_When_Value_IsNot_Converted( IEventMetadata metadata, - JsonSerializerOptions options, UnknownEventDataConverter sut) => sut .Convert( metadata, doc.RootElement, - options, + new JsonSerializerOptions(), () => null) .Should() .BeEquivalentTo( diff --git a/test/Atc.Cosmos.EventStore.Tests/Cosmos/CosmosDeleterTests.cs b/test/Atc.Cosmos.EventStore.Tests/Cosmos/CosmosDeleterTests.cs new file mode 100644 index 0000000..bf2d3b3 --- /dev/null +++ b/test/Atc.Cosmos.EventStore.Tests/Cosmos/CosmosDeleterTests.cs @@ -0,0 +1,50 @@ +using Atc.Cosmos.EventStore.Cosmos; +using Atc.Test; +using Microsoft.Azure.Cosmos; +using NSubstitute; +using Xunit; + +namespace Atc.Cosmos.EventStore.Tests.Cosmos; + +public class CosmosDeleterTests +{ + private readonly ResponseMessage responseMessage; + private readonly Container container; + private readonly IEventStoreContainerProvider containerProvider; + private readonly CosmosDeleter sut; + + public CosmosDeleterTests() + { + responseMessage = Substitute.For(); + responseMessage.IsSuccessStatusCode.Returns(true); + + container = Substitute.For(); + container + .DeleteAllItemsByPartitionKeyStreamAsync(default, default, default) + .ReturnsForAnyArgs(responseMessage); + + containerProvider = Substitute.For(); + containerProvider + .GetStreamContainer() + .Returns(container, returnThese: null); + + sut = new CosmosDeleter(containerProvider); + } + + [Theory, AutoNSubstituteData] + public async Task Should_Use_StreamId_As_PartitionKey( + StreamId streamId, + CancellationToken cancellationToken) + { + await sut.DeleteAsync( + streamId, + cancellationToken); + + _ = container + .Received() + .DeleteAllItemsByPartitionKeyStreamAsync( + new PartitionKey(streamId.Value), + null, + cancellationToken); + } +} \ No newline at end of file diff --git a/test/Atc.Cosmos.EventStore.Tests/EventStoreClientTests.cs b/test/Atc.Cosmos.EventStore.Tests/EventStoreClientTests.cs index 3d5903a..87df0e0 100644 --- a/test/Atc.Cosmos.EventStore.Tests/EventStoreClientTests.cs +++ b/test/Atc.Cosmos.EventStore.Tests/EventStoreClientTests.cs @@ -1,6 +1,5 @@ using System.Collections.ObjectModel; using Atc.Cosmos.EventStore.Cosmos; -using Atc.Cosmos.EventStore.Events; using Atc.Cosmos.EventStore.Streams; using Atc.Test; using AutoFixture.AutoNSubstitute; @@ -104,7 +103,7 @@ await sut } [Theory, AutoNSubstituteData] - internal async Task Should_Throw_When_EventsList_Containes_NullObject( + internal async Task Should_Throw_When_EventsList_Contains_NullObject( EventStoreClient sut, StreamId streamId, Collection events, diff --git a/test/Atc.Cosmos.EventStore.Tests/EventStoreManagementClientTests.cs b/test/Atc.Cosmos.EventStore.Tests/EventStoreManagementClientTests.cs new file mode 100644 index 0000000..6c1c670 --- /dev/null +++ b/test/Atc.Cosmos.EventStore.Tests/EventStoreManagementClientTests.cs @@ -0,0 +1,29 @@ +using Atc.Cosmos.EventStore.Streams; +using Atc.Test; +using AutoFixture.Xunit2; +using FluentAssertions; +using NSubstitute; +using Xunit; + +namespace Atc.Cosmos.EventStore.Tests; + +public class EventStoreManagementClientTests +{ + [Theory, AutoNSubstituteData] + internal async Task Should_DeleteStream( + [Frozen] IStreamDeleter deleter, + EventStoreManagementClient sut, + StreamId streamId, + CancellationToken cancellationToken) + { + await sut.DeleteStreamAsync( + streamId, + cancellationToken: cancellationToken); + + _ = deleter + .Received(1) + .DeleteAsync( + streamId, + cancellationToken); + } +} \ No newline at end of file diff --git a/test/Atc.Cosmos.EventStore.Tests/Streams/StreamInfoReaderTests.cs b/test/Atc.Cosmos.EventStore.Tests/Streams/StreamInfoReaderTests.cs index dd5b655..5c3339b 100644 --- a/test/Atc.Cosmos.EventStore.Tests/Streams/StreamInfoReaderTests.cs +++ b/test/Atc.Cosmos.EventStore.Tests/Streams/StreamInfoReaderTests.cs @@ -23,8 +23,7 @@ internal async Task Should_Convert_Into_StreamResponse( .ReturnsForAnyArgs(expectedMetadata); var info = await sut - .ReadAsync(streamId, cancellationToken) - .ConfigureAwait(false); + .ReadAsync(streamId, cancellationToken); info .State @@ -57,8 +56,7 @@ internal async Task Should_Read_Metadata_From_Stream( .ReturnsForAnyArgs(expectedMetadata); await sut - .ReadAsync(streamId, cancellationToken) - .ConfigureAwait(false); + .ReadAsync(streamId, cancellationToken); _ = metadataReader .Received(1) diff --git a/version.json b/version.json index db87276..f03d338 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.13", + "version": "1.14", "assemblyVersion": { "precision": "revision" },