diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index b9b042301..c0c57f739 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net5.0, net6.0, net7.0 ] + framework: [ net6.0, net7.0, net8.0 ] os: [ ubuntu-latest ] test: [ Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement ] configuration: [ release ] @@ -33,9 +33,9 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 5.0.x 6.0.x 7.0.x + 8.0.x - name: Compile shell: bash run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16e37e13d..31add45a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,9 @@ on: pull_request: push: branches: - - master + - master tags: - - v* + - v* jobs: test: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f3d7df4f9..239ea739a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net5.0, net6.0, net7.0 ] + framework: [ net6.0, net7.0, net8.0 ] os: [ ubuntu-latest, windows-latest ] runs-on: ${{ matrix.os }} name: scan-vulnerabilities/${{ matrix.os }}/${{ matrix.framework }} @@ -25,9 +25,9 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 5.0.x 6.0.x 7.0.x + 8.0.x - name: Scan for Vulnerabilities shell: bash run: | @@ -43,13 +43,13 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net5.0, net6.0, net7.0 ] + framework: [ net8.0 ] services: esdb: image: ghcr.io/eventstore/eventstore:lts env: EVENTSTORE_INSECURE: true - EVENTSTORE_MEMDB: true + EVENTSTORE_MEM_DB: false EVENTSTORE_RUN_PROJECTIONS: all EVENTSTORE_START_STANDARD_PROJECTIONS: true ports: @@ -62,9 +62,7 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 5.0.x - 6.0.x - 7.0.x + 8.0.x - name: Compile shell: bash run: | @@ -79,7 +77,7 @@ jobs: strategy: fail-fast: false matrix: - framework: [ net5.0, net6.0, net7.0 ] + framework: [ net6.0, net7.0, net8.0 ] os: [ ubuntu-latest, windows-latest ] configuration: [ release ] runs-on: ${{ matrix.os }} @@ -94,9 +92,9 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 5.0.x 6.0.x 7.0.x + 8.0.x - name: Compile shell: bash run: | @@ -132,9 +130,9 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: | - 5.0.x 6.0.x 7.0.x + 8.0.x - name: Dotnet Pack shell: bash run: | diff --git a/Directory.Build.props b/Directory.Build.props index 3ed57a184..eecbb8220 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,6 @@ - net5.0;net6.0;net7.0; - x64 + net6.0;net7.0;net8.0 true enable enable @@ -14,7 +13,7 @@ pdbonly true - 2.49.0 - 2.50.0 + 2.59.0 + 2.59.0 diff --git a/EventStore.Client.sln.DotSettings b/EventStore.Client.sln.DotSettings index 16d970453..f97c72463 100644 --- a/EventStore.Client.sln.DotSettings +++ b/EventStore.Client.sln.DotSettings @@ -26,16 +26,16 @@ &lt;inspection_tool class="UnnecessaryReturnJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; &lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; &lt;Language id="CSS"&gt; - &lt;Rearrange&gt;true&lt;/Rearrange&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; &lt;/Language&gt; &lt;Language id="EditorConfig"&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="HTML"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; &lt;Rearrange&gt;true&lt;/Rearrange&gt; - &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="HTTP Request"&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; @@ -53,9 +53,9 @@ &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="JavaScript"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; &lt;Rearrange&gt;true&lt;/Rearrange&gt; - &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="Markdown"&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; @@ -73,9 +73,9 @@ &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="XML"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; &lt;Rearrange&gt;true&lt;/Rearrange&gt; - &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="yaml"&gt; &lt;Reformat&gt;true&lt;/Reformat&gt; @@ -402,4 +402,5 @@ True True True + True True \ No newline at end of file diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index a3644b0a7..54497a508 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -1,10 +1,15 @@ - net5.0;net6.0;net7.0 + net8.0 enable enable true Exe preview + + + + + \ No newline at end of file diff --git a/samples/Samples.sln.DotSettings b/samples/Samples.sln.DotSettings index 16d970453..c341e4095 100644 --- a/samples/Samples.sln.DotSettings +++ b/samples/Samples.sln.DotSettings @@ -399,6 +399,7 @@ True True True + True True True True diff --git a/samples/appending-events/Program.cs b/samples/appending-events/Program.cs index 4a908a3d1..2e1bb758f 100644 --- a/samples/appending-events/Program.cs +++ b/samples/appending-events/Program.cs @@ -1,161 +1,169 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client; - -namespace appending_events { - class Program { - static async Task Main(string[] args) { - var settings = EventStoreClientSettings.Create("esdb://localhost:2113?tls=false"); - settings.OperationOptions.ThrowOnAppendFailure = false; - await using var client = new EventStoreClient( - settings - ); - await AppendToStream(client); - await AppendWithConcurrencyCheck(client); - await AppendWithNoStream(client); - await AppendWithSameId(client); - - return 0; - } +#pragma warning disable CS8321 // Local function is declared but never used + +var settings = EventStoreClientSettings.Create("esdb://localhost:2113?tls=false"); + +settings.OperationOptions.ThrowOnAppendFailure = false; + +await using var client = new EventStoreClient(settings); + +await AppendToStream(client); +await AppendWithConcurrencyCheck(client); +await AppendWithNoStream(client); +await AppendWithSameId(client); + +return; - private static async Task AppendToStream(EventStoreClient client) { - #region append-to-stream - var eventData = new EventData( - Uuid.NewUuid(), - "some-event", - Encoding.UTF8.GetBytes("{\"id\": \"1\" \"value\": \"some value\"}") - ); - - await client.AppendToStreamAsync( - "some-stream", - StreamState.NoStream, - new List { - eventData - }); - #endregion append-to-stream +static async Task AppendToStream(EventStoreClient client) { + #region append-to-stream + + var eventData = new EventData( + Uuid.NewUuid(), + "some-event", + "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() + ); + + await client.AppendToStreamAsync( + "some-stream", + StreamState.NoStream, + new List { + eventData } + ); + + #endregion append-to-stream +} + +static async Task AppendWithSameId(EventStoreClient client) { + #region append-duplicate-event + + var eventData = new EventData( + Uuid.NewUuid(), + "some-event", + "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() + ); - private static async Task AppendWithSameId(EventStoreClient client) { - #region append-duplicate-event - var eventData = new EventData( - Uuid.NewUuid(), - "some-event", - Encoding.UTF8.GetBytes("{\"id\": \"1\" \"value\": \"some value\"}") - ); - - await client.AppendToStreamAsync( - "same-event-stream", - StreamState.Any, - new List { - eventData - }); - - // attempt to append the same event again - await client.AppendToStreamAsync( - "same-event-stream", - StreamState.Any, - new List { - eventData - }); - #endregion append-duplicate-event + await client.AppendToStreamAsync( + "same-event-stream", + StreamState.Any, + new List { + eventData } + ); + + // attempt to append the same event again + await client.AppendToStreamAsync( + "same-event-stream", + StreamState.Any, + new List { + eventData + } + ); + + #endregion append-duplicate-event +} - private static async Task AppendWithNoStream(EventStoreClient client) { - #region append-with-no-stream - var eventDataOne = new EventData( - Uuid.NewUuid(), - "some-event", - Encoding.UTF8.GetBytes("{\"id\": \"1\" \"value\": \"some value\"}") - ); - - var eventDataTwo = new EventData( - Uuid.NewUuid(), - "some-event", - Encoding.UTF8.GetBytes("{\"id\": \"2\" \"value\": \"some other value\"}") - ); - - await client.AppendToStreamAsync( - "no-stream-stream", - StreamState.NoStream, - new List { - eventDataOne - }); - - // attempt to append the same event again - await client.AppendToStreamAsync( - "no-stream-stream", - StreamState.NoStream, - new List { - eventDataTwo - }); - #endregion append-with-no-stream +static async Task AppendWithNoStream(EventStoreClient client) { + #region append-with-no-stream + + var eventDataOne = new EventData( + Uuid.NewUuid(), + "some-event", + "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() + ); + + var eventDataTwo = new EventData( + Uuid.NewUuid(), + "some-event", + "{\"id\": \"2\" \"value\": \"some other value\"}"u8.ToArray() + ); + + await client.AppendToStreamAsync( + "no-stream-stream", + StreamState.NoStream, + new List { + eventDataOne } + ); + + // attempt to append the same event again + await client.AppendToStreamAsync( + "no-stream-stream", + StreamState.NoStream, + new List { + eventDataTwo + } + ); + + #endregion append-with-no-stream +} - private static async Task AppendWithConcurrencyCheck(EventStoreClient client) { - await client.AppendToStreamAsync("concurrency-stream", StreamRevision.None, - new[] {new EventData(Uuid.NewUuid(), "-", ReadOnlyMemory.Empty)}); - #region append-with-concurrency-check - - var clientOneRead = client.ReadStreamAsync( - Direction.Forwards, - "concurrency-stream", - StreamPosition.Start); - var clientOneRevision = (await clientOneRead.LastAsync()).Event.EventNumber.ToUInt64(); - - var clientTwoRead = client.ReadStreamAsync(Direction.Forwards, "concurrency-stream", StreamPosition.Start); - var clientTwoRevision = (await clientTwoRead.LastAsync()).Event.EventNumber.ToUInt64(); - - var clientOneData = new EventData( - Uuid.NewUuid(), - "some-event", - Encoding.UTF8.GetBytes("{\"id\": \"1\" \"value\": \"clientOne\"}") - ); - - await client.AppendToStreamAsync( - "no-stream-stream", - clientOneRevision, - new List { - clientOneData - }); - - var clientTwoData = new EventData( - Uuid.NewUuid(), - "some-event", - Encoding.UTF8.GetBytes("{\"id\": \"2\" \"value\": \"clientTwo\"}") - ); - - await client.AppendToStreamAsync( - "no-stream-stream", - clientTwoRevision, - new List { - clientTwoData - }); - #endregion append-with-concurrency-check +static async Task AppendWithConcurrencyCheck(EventStoreClient client) { + await client.AppendToStreamAsync( + "concurrency-stream", + StreamRevision.None, + new[] { new EventData(Uuid.NewUuid(), "-", ReadOnlyMemory.Empty) } + ); + + #region append-with-concurrency-check + + var clientOneRead = client.ReadStreamAsync( + Direction.Forwards, + "concurrency-stream", + StreamPosition.Start + ); + + var clientOneRevision = (await clientOneRead.LastAsync()).Event.EventNumber.ToUInt64(); + + var clientTwoRead = client.ReadStreamAsync(Direction.Forwards, "concurrency-stream", StreamPosition.Start); + var clientTwoRevision = (await clientTwoRead.LastAsync()).Event.EventNumber.ToUInt64(); + + var clientOneData = new EventData( + Uuid.NewUuid(), + "some-event", + "{\"id\": \"1\" \"value\": \"clientOne\"}"u8.ToArray() + ); + + await client.AppendToStreamAsync( + "no-stream-stream", + clientOneRevision, + new List { + clientOneData } - - protected async Task AppendOverridingUserCredentials(EventStoreClient client, CancellationToken cancellationToken) - { - var eventData = new EventData( - Uuid.NewUuid(), - "TestEvent", - Encoding.UTF8.GetBytes("{\"id\": \"1\" \"value\": \"some value\"}") - ); - - #region overriding-user-credentials - await client.AppendToStreamAsync( - "some-stream", - StreamState.Any, - new[] { eventData }, - userCredentials: new UserCredentials("admin", "changeit"), - cancellationToken: cancellationToken - ); - #endregion overriding-user-credentials + ); + + var clientTwoData = new EventData( + Uuid.NewUuid(), + "some-event", + "{\"id\": \"2\" \"value\": \"clientTwo\"}"u8.ToArray() + ); + + await client.AppendToStreamAsync( + "no-stream-stream", + clientTwoRevision, + new List { + clientTwoData } - } + ); + + #endregion append-with-concurrency-check +} + +static async Task AppendOverridingUserCredentials(EventStoreClient client, CancellationToken cancellationToken) { + var eventData = new EventData( + Uuid.NewUuid(), + "TestEvent", + "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() + ); + + #region overriding-user-credentials + + await client.AppendToStreamAsync( + "some-stream", + StreamState.Any, + new[] { eventData }, + userCredentials: new UserCredentials("admin", "changeit"), + cancellationToken: cancellationToken + ); + + #endregion overriding-user-credentials } diff --git a/samples/appending-events/appending-events.csproj b/samples/appending-events/appending-events.csproj index 34b8399e9..fb93d800e 100644 --- a/samples/appending-events/appending-events.csproj +++ b/samples/appending-events/appending-events.csproj @@ -6,7 +6,7 @@ - + diff --git a/samples/connecting-to-a-cluster/Program.cs b/samples/connecting-to-a-cluster/Program.cs index 67e4fba6d..a31666fc6 100644 --- a/samples/connecting-to-a-cluster/Program.cs +++ b/samples/connecting-to-a-cluster/Program.cs @@ -1,34 +1,33 @@ -using System; -using System.Net; -using EventStore.Client; - -namespace connecting_to_a_cluster { - class Program { - static void Main(string[] args) { - } - - private static void ConnectingToACluster() { - #region connecting-to-a-cluster - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://localhost:1114,localhost:2114,localhost:3114") - ); - #endregion connecting-to-a-cluster - } - - private static void ProvidingDefaultCredentials() { - #region providing-default-credentials - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://admin:changeit@localhost:1114,localhost:2114,localhost:3114") - ); - #endregion providing-default-credentials - } - - private static void ConnectingToAClusterComplex() { - #region connecting-to-a-cluster-complex - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://admin:changeit@localhost:1114,localhost:2114,localhost:3114?DiscoveryInterval=30000;GossipTimeout=10000;NodePreference=leader;MaxDiscoverAttempts=5") - ); - #endregion connecting-to-a-cluster-complex - } - } +#pragma warning disable CS8321 // Local function is declared but never used + +static void ConnectingToACluster() { + #region connecting-to-a-cluster + + using var client = new EventStoreClient( + EventStoreClientSettings.Create("esdb://localhost:1114,localhost:2114,localhost:3114") + ); + + #endregion connecting-to-a-cluster +} + +static void ProvidingDefaultCredentials() { + #region providing-default-credentials + + using var client = new EventStoreClient( + EventStoreClientSettings.Create("esdb://admin:changeit@localhost:1114,localhost:2114,localhost:3114") + ); + + #endregion providing-default-credentials } + +static void ConnectingToAClusterComplex() { + #region connecting-to-a-cluster-complex + + using var client = new EventStoreClient( + EventStoreClientSettings.Create( + "esdb://admin:changeit@localhost:1114,localhost:2114,localhost:3114?DiscoveryInterval=30000;GossipTimeout=10000;NodePreference=leader;MaxDiscoverAttempts=5" + ) + ); + + #endregion connecting-to-a-cluster-complex +} \ No newline at end of file diff --git a/samples/connecting-to-a-cluster/connecting-to-a-cluster.csproj b/samples/connecting-to-a-cluster/connecting-to-a-cluster.csproj index 1d8ff5b7d..4754bd0e2 100644 --- a/samples/connecting-to-a-cluster/connecting-to-a-cluster.csproj +++ b/samples/connecting-to-a-cluster/connecting-to-a-cluster.csproj @@ -6,7 +6,7 @@ - + diff --git a/samples/connecting-to-a-single-node/DemoInterceptor.cs b/samples/connecting-to-a-single-node/DemoInterceptor.cs index 6f01bf72a..fc68b70a2 100644 --- a/samples/connecting-to-a-single-node/DemoInterceptor.cs +++ b/samples/connecting-to-a-single-node/DemoInterceptor.cs @@ -1,28 +1,31 @@ -using System; using Grpc.Core; using Grpc.Core.Interceptors; -namespace connecting_to_a_single_node { - #region interceptor - public class DemoInterceptor : Interceptor { - public override AsyncServerStreamingCall - AsyncServerStreamingCall( - TRequest request, - ClientInterceptorContext context, - AsyncServerStreamingCallContinuation continuation) { - Console.WriteLine($"AsyncServerStreamingCall: {context.Method.FullName}"); +namespace connecting_to_a_single_node; - return base.AsyncServerStreamingCall(request, context, continuation); - } +#region interceptor - public override AsyncClientStreamingCall - AsyncClientStreamingCall( - ClientInterceptorContext context, - AsyncClientStreamingCallContinuation continuation) { - Console.WriteLine($"AsyncClientStreamingCall: {context.Method.FullName}"); +public class DemoInterceptor : Interceptor { + public override AsyncServerStreamingCall + AsyncServerStreamingCall( + TRequest request, + ClientInterceptorContext context, + AsyncServerStreamingCallContinuation continuation + ) { + Console.WriteLine($"AsyncServerStreamingCall: {context.Method.FullName}"); - return base.AsyncClientStreamingCall(context, continuation); - } + return base.AsyncServerStreamingCall(request, context, continuation); + } + + public override AsyncClientStreamingCall + AsyncClientStreamingCall( + ClientInterceptorContext context, + AsyncClientStreamingCallContinuation continuation + ) { + Console.WriteLine($"AsyncClientStreamingCall: {context.Method.FullName}"); + + return base.AsyncClientStreamingCall(context, continuation); } - #endregion interceptor } + +#endregion interceptor \ No newline at end of file diff --git a/samples/connecting-to-a-single-node/Program.cs b/samples/connecting-to-a-single-node/Program.cs index 192327ba5..f50e1689f 100644 --- a/samples/connecting-to-a-single-node/Program.cs +++ b/samples/connecting-to-a-single-node/Program.cs @@ -1,80 +1,85 @@ -using System; -using System.Net.Http; -using EventStore.Client; +using connecting_to_a_single_node; -namespace connecting_to_a_single_node { - class Program { - static void Main(string[] args) { - } +#pragma warning disable CS8321 // Local function is declared but never used - private static void SimpleConnection() { - #region creating-simple-connection - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://localhost:2113") - ); - #endregion creating-simple-connection - } +static void SimpleConnection() { + #region creating-simple-connection - private static void ProvidingDefaultCredentials() { - #region providing-default-credentials - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://admin:changeit@localhost:2113") - ); - #endregion providing-default-credentials - } + using var client = new EventStoreClient(EventStoreClientSettings.Create("esdb://localhost:2113")); - private static void SpecifyingAConnectionName() { - #region setting-the-connection-name - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://admin:changeit@localhost:2113?ConnectionName=SomeConnection") - ); - #endregion setting-the-connection-name - } + #endregion creating-simple-connection +} - private static void OverridingTheTimeout() { - #region overriding-timeout - using var client = new EventStoreClient( - EventStoreClientSettings.Create($"esdb://admin:changeit@localhost:2113?OperationTimeout=30000") - ); - #endregion overriding-timeout - } - - private static void CombiningSettings() { - #region overriding-timeout - using var client = new EventStoreClient( - EventStoreClientSettings.Create($"esdb://admin:changeit@localhost:2113?ConnectionName=SomeConnection&OperationTimeout=30000") - ); - #endregion overriding-timeout - } +static void ProvidingDefaultCredentials() { + #region providing-default-credentials - private static void CreatingAnInterceptor() { - #region adding-an-interceptor - var settings = new EventStoreClientSettings { - Interceptors = new[] {new DemoInterceptor()}, - ConnectivitySettings = { - Address = new Uri("https://localhost:2113") - } - }; - #endregion adding-an-interceptor - - var client = new EventStoreClient(settings); - } + using var client = new EventStoreClient(EventStoreClientSettings.Create("esdb://admin:changeit@localhost:2113")); + + #endregion providing-default-credentials +} + +static void SpecifyingAConnectionName() { + #region setting-the-connection-name + + using var client = new EventStoreClient( + EventStoreClientSettings.Create("esdb://admin:changeit@localhost:2113?ConnectionName=SomeConnection") + ); + + #endregion setting-the-connection-name +} + +static void OverridingTheTimeout() { + #region overriding-timeout - private static void CustomHttpMessageHandler() { - #region adding-an-custom-http-message-handler - var settings = new EventStoreClientSettings { - CreateHttpMessageHandler = () => - new HttpClientHandler { - ServerCertificateCustomValidationCallback = - (sender, cert, chain, sslPolicyErrors) => true - }, - ConnectivitySettings = { - Address = new Uri("https://localhost:2113") - } - }; - #endregion adding-an-custom-http-message-handler - - var client = new EventStoreClient(settings); + using var client = new EventStoreClient( + EventStoreClientSettings.Create($"esdb://admin:changeit@localhost:2113?OperationTimeout=30000") + ); + + #endregion overriding-timeout +} + +static void CombiningSettings() { + #region overriding-timeout + + using var client = new EventStoreClient( + EventStoreClientSettings.Create( + $"esdb://admin:changeit@localhost:2113?ConnectionName=SomeConnection&OperationTimeout=30000" + ) + ); + + #endregion overriding-timeout +} + +static void CreatingAnInterceptor() { + #region adding-an-interceptor + + var settings = new EventStoreClientSettings { + Interceptors = new[] { new DemoInterceptor() }, + ConnectivitySettings = { + Address = new Uri("https://localhost:2113") } - } + }; + + #endregion adding-an-interceptor + + var client = new EventStoreClient(settings); } + +static void CustomHttpMessageHandler() { + #region adding-an-custom-http-message-handler + + var settings = new EventStoreClientSettings { + CreateHttpMessageHandler = () => + new HttpClientHandler { + ServerCertificateCustomValidationCallback = + (sender, cert, chain, sslPolicyErrors) => true + }, + ConnectivitySettings = { + Address = new Uri("https://localhost:2113") + } + }; + + #endregion adding-an-custom-http-message-handler + + var client = new EventStoreClient(settings); +} \ No newline at end of file diff --git a/samples/connecting-to-a-single-node/connecting-to-a-single-node.csproj b/samples/connecting-to-a-single-node/connecting-to-a-single-node.csproj index 337b66b85..4ef794ee4 100644 --- a/samples/connecting-to-a-single-node/connecting-to-a-single-node.csproj +++ b/samples/connecting-to-a-single-node/connecting-to-a-single-node.csproj @@ -5,7 +5,7 @@ - + diff --git a/samples/persistent-subscriptions/Program.cs b/samples/persistent-subscriptions/Program.cs index f26348917..6ea61fa67 100644 --- a/samples/persistent-subscriptions/Program.cs +++ b/samples/persistent-subscriptions/Program.cs @@ -1,235 +1,298 @@ -using System; -using System.Threading.Tasks; -using EventStore.Client; - -namespace persistent_subscriptions -{ - class Program - { - static async Task Main(string[] args) { - await using var client = new EventStorePersistentSubscriptionsClient( - EventStoreClientSettings.Create("esdb://localhost:2113?tls=false") - ); - await CreatePersistentSubscription(client); - await UpdatePersistentSubscription(client); - await ConnectToPersistentSubscriptionToStream(client); - await CreatePersistentSubscriptionToAll(client); - await ConnectToPersistentSubscriptionToAll(client); - await ConnectToPersistentSubscriptionWithManualAcks(client); - await GetPersistentSubscriptionToStreamInfo(client); - await GetPersistentSubscriptionToAllInfo(client); - await ReplayParkedToStream(client); - await ReplayParkedToAll(client); - await ListPersistentSubscriptionsToStream(client); - await ListPersistentSubscriptionsToAll(client); - await ListAllPersistentSubscriptions(client); - await RestartPersistentSubscriptionSubsystem(client); - await DeletePersistentSubscription(client); - } - - static async Task CreatePersistentSubscription(EventStorePersistentSubscriptionsClient client) { - #region create-persistent-subscription-to-stream - var userCredentials = new UserCredentials("admin", "changeit"); - - var settings = new PersistentSubscriptionSettings(); - await client.CreateToStreamAsync( - "test-stream", - "subscription-group", - settings, - userCredentials: userCredentials); - #endregion create-persistent-subscription-to-stream - } - - static async Task ConnectToPersistentSubscriptionToStream(EventStorePersistentSubscriptionsClient client) { - #region subscribe-to-persistent-subscription-to-stream - var subscription = await client.SubscribeToStreamAsync( - "test-stream", - "subscription-group", - async (subscription, evnt, retryCount, cancellationToken) => { - await HandleEvent(evnt); - await subscription.Ack(evnt); - }, (subscription, dropReason, exception) => { - Console.WriteLine($"Subscription was dropped due to {dropReason}. {exception}"); - }); - #endregion subscribe-to-persistent-subscription-to-stream - } - - static async Task CreatePersistentSubscriptionToAll(EventStorePersistentSubscriptionsClient client) { - #region create-persistent-subscription-to-all - var userCredentials = new UserCredentials("admin", "changeit"); - var filter = StreamFilter.Prefix("test"); - - var settings = new PersistentSubscriptionSettings(); - await client.CreateToAllAsync( - "subscription-group", - filter, - settings, - userCredentials: userCredentials); - #endregion create-persistent-subscription-to-all - } - - static async Task ConnectToPersistentSubscriptionToAll(EventStorePersistentSubscriptionsClient client) { - #region subscribe-to-persistent-subscription-to-all - await client.SubscribeToAllAsync( - "subscription-group", - async (subscription, evnt, retryCount, cancellationToken) => { - await HandleEvent(evnt); - }, (subscription, dropReason, exception) => { - Console.WriteLine($"Subscription was dropped due to {dropReason}. {exception}"); - }); - #endregion subscribe-to-persistent-subscription-to-all - } - - static async Task ConnectToPersistentSubscriptionWithManualAcks(EventStorePersistentSubscriptionsClient client) { - #region subscribe-to-persistent-subscription-with-manual-acks - var subscription = await client.SubscribeToStreamAsync( - "test-stream", - "subscription-group", - async (subscription, evnt, retryCount, cancellationToken) => { - try { - await HandleEvent(evnt); - await subscription.Ack(evnt); - } catch (UnrecoverableException ex) { - await subscription.Nack(PersistentSubscriptionNakEventAction.Park, ex.Message, evnt); - } - }, (subscription, dropReason, exception) => { - Console.WriteLine($"Subscription was dropped due to {dropReason}. {exception}"); - }); - #endregion subscribe-to-persistent-subscription-with-manual-acks - } - - static async Task UpdatePersistentSubscription(EventStorePersistentSubscriptionsClient client) { - #region update-persistent-subscription - var userCredentials = new UserCredentials("admin", "changeit"); - var settings = new PersistentSubscriptionSettings( - resolveLinkTos: true, - checkPointLowerBound: 20); - - await client.UpdateToStreamAsync( - "test-stream", - "subscription-group", - settings, - userCredentials: userCredentials); - #endregion update-persistent-subscription - } - - static async Task DeletePersistentSubscription(EventStorePersistentSubscriptionsClient client) { - #region delete-persistent-subscription - var userCredentials = new UserCredentials("admin", "changeit"); - await client.DeleteToStreamAsync( - "test-stream", - "subscription-group", - userCredentials: userCredentials); - #endregion delete-persistent-subscription - } - - static async Task GetPersistentSubscriptionToStreamInfo(EventStorePersistentSubscriptionsClient client) { - #region get-persistent-subscription-to-stream-info - - var userCredentials = new UserCredentials("admin", "changeit"); - var info = await client.GetInfoToStreamAsync( - "test-stream", - "subscription-group", - userCredentials: userCredentials); - - Console.WriteLine($"GroupName: {info.GroupName}, EventSource: {info.EventSource}, Status: {info.Status}"); - - #endregion get-persistent-subscription-to-stream-info - } - - static async Task GetPersistentSubscriptionToAllInfo(EventStorePersistentSubscriptionsClient client) { - #region get-persistent-subscription-to-all-info - - var userCredentials = new UserCredentials("admin", "changeit"); - var info = await client.GetInfoToAllAsync( - "subscription-group", - userCredentials: userCredentials); - - Console.WriteLine($"GroupName: {info.GroupName}, EventSource: {info.EventSource}, Status: {info.Status}"); - - #endregion get-persistent-subscription-to-all-info - } - - static async Task ReplayParkedToStream(EventStorePersistentSubscriptionsClient client) { - #region replay-parked-of-persistent-subscription-to-stream - - var userCredentials = new UserCredentials("admin", "changeit"); - await client.ReplayParkedMessagesToStreamAsync( - "test-stream", - "subscription-group", - stopAt: 10, - userCredentials: userCredentials); - - #endregion persistent-subscription-replay-parked-to-stream - } - - static async Task ReplayParkedToAll(EventStorePersistentSubscriptionsClient client) { - #region replay-parked-of-persistent-subscription-to-all - - var userCredentials = new UserCredentials("admin", "changeit"); - await client.ReplayParkedMessagesToAllAsync( - "subscription-group", - stopAt: 10, - userCredentials: userCredentials); - - #endregion replay-parked-of-persistent-subscription-to-all - } - - static async Task ListPersistentSubscriptionsToStream(EventStorePersistentSubscriptionsClient client) { - #region list-persistent-subscriptions-to-stream - - var userCredentials = new UserCredentials("admin", "changeit"); - var subscriptions = await client.ListToStreamAsync( - "test-stream", - userCredentials: userCredentials); - - foreach (var s in subscriptions) { - Console.WriteLine($"GroupName: {s.GroupName}, EventSource: {s.EventSource}, Status: {s.Status}"); - } - - #endregion list-persistent-subscriptions-to-stream - } - - static async Task ListPersistentSubscriptionsToAll(EventStorePersistentSubscriptionsClient client) { - #region list-persistent-subscriptions-to-all - - var userCredentials = new UserCredentials("admin", "changeit"); - var subscriptions = await client.ListToAllAsync(userCredentials: userCredentials); - - foreach (var s in subscriptions) { - Console.WriteLine($"GroupName: {s.GroupName}, EventSource: {s.EventSource}, Status: {s.Status}"); - } - - #endregion list-persistent-subscriptions-to-all - } - - static async Task ListAllPersistentSubscriptions(EventStorePersistentSubscriptionsClient client) { - #region list-persistent-subscriptions - - var userCredentials = new UserCredentials("admin", "changeit"); - var subscriptions = await client.ListAllAsync(userCredentials: userCredentials); - - foreach (var s in subscriptions) { - Console.WriteLine($"GroupName: {s.GroupName}, EventSource: {s.EventSource}, Status: {s.Status}"); - } - - #endregion list-persistent-subscriptions - } - - static async Task RestartPersistentSubscriptionSubsystem(EventStorePersistentSubscriptionsClient client) { - #region restart-persistent-subscription-subsystem - - var userCredentials = new UserCredentials("admin", "changeit"); - await client.RestartSubsystemAsync(userCredentials: userCredentials); - - #endregion restart-persistent-subscription-subsystem - } - - static Task HandleEvent(ResolvedEvent evnt) { - return Task.CompletedTask; - } - - class UnrecoverableException : Exception { - } - } +await using var client = new EventStorePersistentSubscriptionsClient( + EventStoreClientSettings.Create("esdb://localhost:2113?tls=false&tlsVerifyCert=false") +); + +await DeletePersistentSubscription(client); +await DeletePersistentSubscriptionToAll(client); + +await CreatePersistentSubscription(client); +await UpdatePersistentSubscription(client); +await ConnectToPersistentSubscriptionToStream(client); +await CreatePersistentSubscriptionToAll(client); +await ConnectToPersistentSubscriptionToAll(client); +await ConnectToPersistentSubscriptionWithManualAcks(client); +await GetPersistentSubscriptionToStreamInfo(client); +await GetPersistentSubscriptionToAllInfo(client); +await ReplayParkedToStream(client); +await ReplayParkedToAll(client); +await ListPersistentSubscriptionsToStream(client); +await ListPersistentSubscriptionsToAll(client); +await ListAllPersistentSubscriptions(client); +await RestartPersistentSubscriptionSubsystem(client); + +await DeletePersistentSubscription(client); +await DeletePersistentSubscriptionToAll(client); + +return; + +static async Task CreatePersistentSubscription(EventStorePersistentSubscriptionsClient client) { + #region create-persistent-subscription-to-stream + + var userCredentials = new UserCredentials("admin", "changeit"); + + var settings = new PersistentSubscriptionSettings(); + await client.CreateToStreamAsync( + "test-stream", + "subscription-group", + settings, + userCredentials: userCredentials + ); + + Console.WriteLine("Subscription to stream created"); + #endregion create-persistent-subscription-to-stream +} + +static async Task ConnectToPersistentSubscriptionToStream(EventStorePersistentSubscriptionsClient client) { + #region subscribe-to-persistent-subscription-to-stream + + var subscription = await client.SubscribeToStreamAsync( + "test-stream", + "subscription-group", + async (subscription, evnt, retryCount, cancellationToken) => { + await HandleEvent(evnt); + await subscription.Ack(evnt); + }, + (subscription, dropReason, exception) => { + Console.WriteLine($"Subscription to stream was dropped due to {dropReason}. {exception?.Message}"); + } + ); + + Console.WriteLine("Subscription to stream started"); + #endregion subscribe-to-persistent-subscription-to-stream +} + +static async Task CreatePersistentSubscriptionToAll(EventStorePersistentSubscriptionsClient client) { + #region create-persistent-subscription-to-all + + var userCredentials = new UserCredentials("admin", "changeit"); + var filter = StreamFilter.Prefix("test"); + + var settings = new PersistentSubscriptionSettings(); + await client.CreateToAllAsync( + "subscription-group", + filter, + settings, + userCredentials: userCredentials + ); + + Console.WriteLine("Subscription to all created"); + #endregion create-persistent-subscription-to-all +} + +static async Task ConnectToPersistentSubscriptionToAll(EventStorePersistentSubscriptionsClient client) { + #region subscribe-to-persistent-subscription-to-all + + await client.SubscribeToAllAsync( + "subscription-group", + async (subscription, evnt, retryCount, cancellationToken) => { await HandleEvent(evnt); }, + (subscription, dropReason, exception) => { + Console.WriteLine($"Subscription to all was dropped due to {dropReason}. {exception?.Message}"); + } + ); + + Console.WriteLine("Subscription to all started"); + #endregion subscribe-to-persistent-subscription-to-all +} + +static async Task ConnectToPersistentSubscriptionWithManualAcks(EventStorePersistentSubscriptionsClient client) { + #region subscribe-to-persistent-subscription-with-manual-acks + + var subscription = await client.SubscribeToStreamAsync( + "test-stream", + "subscription-group", + async (subscription, evnt, retryCount, cancellationToken) => { + try { + await HandleEvent(evnt); + await subscription.Ack(evnt); + } + catch (UnrecoverableException ex) { + await subscription.Nack(PersistentSubscriptionNakEventAction.Park, ex.Message, evnt); + } + }, + (subscription, dropReason, exception) => { + Console.WriteLine($"Subscription to stream with manual acks was dropped due to {dropReason}. {exception?.Message}"); + } + ); + + Console.WriteLine("Subscription to stream with manual acks started"); + #endregion subscribe-to-persistent-subscription-with-manual-acks +} + +static async Task UpdatePersistentSubscription(EventStorePersistentSubscriptionsClient client) { + #region update-persistent-subscription + + var userCredentials = new UserCredentials("admin", "changeit"); + var settings = new PersistentSubscriptionSettings(true, checkPointLowerBound: 20); + + await client.UpdateToStreamAsync( + "test-stream", + "subscription-group", + settings, + userCredentials: userCredentials + ); + + Console.WriteLine("Subscription updated"); + #endregion update-persistent-subscription +} + +static async Task DeletePersistentSubscription(EventStorePersistentSubscriptionsClient client) { + #region delete-persistent-subscription + + try { + var userCredentials = new UserCredentials("admin", "changeit"); + await client.DeleteToStreamAsync( + "test-stream", + "subscription-group", + userCredentials: userCredentials + ); + + Console.WriteLine("Subscription to stream deleted"); + } + catch (PersistentSubscriptionNotFoundException) { + // ignore + } + catch (Exception ex) { + Console.WriteLine($"Subscription to stream delete error: {ex.GetType()} {ex.Message}"); + } + + #endregion delete-persistent-subscription +} + +static async Task DeletePersistentSubscriptionToAll(EventStorePersistentSubscriptionsClient client) { + #region delete-persistent-subscription-to-all + + try { + var userCredentials = new UserCredentials("admin", "changeit"); + await client.DeleteToAllAsync( + "subscription-group", + userCredentials: userCredentials + ); + + Console.WriteLine("Subscription to all deleted"); + } + catch (PersistentSubscriptionNotFoundException) { + // ignore + } + catch (Exception ex) { + Console.WriteLine($"Subscription to all delete error: {ex.GetType()} {ex.Message}"); + } + + #endregion delete-persistent-subscription-to-all +} + +static async Task GetPersistentSubscriptionToStreamInfo(EventStorePersistentSubscriptionsClient client) { + #region get-persistent-subscription-to-stream-info + + var userCredentials = new UserCredentials("admin", "changeit"); + var info = await client.GetInfoToStreamAsync( + "test-stream", + "subscription-group", + userCredentials: userCredentials + ); + + Console.WriteLine($"GroupName: {info.GroupName} EventSource: {info.EventSource} Status: {info.Status}"); + + #endregion get-persistent-subscription-to-stream-info +} + +static async Task GetPersistentSubscriptionToAllInfo(EventStorePersistentSubscriptionsClient client) { + #region get-persistent-subscription-to-all-info + + var userCredentials = new UserCredentials("admin", "changeit"); + var info = await client.GetInfoToAllAsync( + "subscription-group", + userCredentials: userCredentials + ); + + Console.WriteLine($"GroupName: {info.GroupName} EventSource: {info.EventSource} Status: {info.Status}"); + + #endregion get-persistent-subscription-to-all-info +} + +static async Task ReplayParkedToStream(EventStorePersistentSubscriptionsClient client) { + #region replay-parked-of-persistent-subscription-to-stream + + var userCredentials = new UserCredentials("admin", "changeit"); + await client.ReplayParkedMessagesToStreamAsync( + "test-stream", + "subscription-group", + 10, + userCredentials: userCredentials + ); + + Console.WriteLine("Replay of parked messages to stream requested"); + #endregion persistent-subscription-replay-parked-to-stream +} + +static async Task ReplayParkedToAll(EventStorePersistentSubscriptionsClient client) { + #region replay-parked-of-persistent-subscription-to-all + + var userCredentials = new UserCredentials("admin", "changeit"); + await client.ReplayParkedMessagesToAllAsync( + "subscription-group", + 10, + userCredentials: userCredentials + ); + + Console.WriteLine("Replay of parked messages to all requested"); + #endregion replay-parked-of-persistent-subscription-to-all } + +static async Task ListPersistentSubscriptionsToStream(EventStorePersistentSubscriptionsClient client) { + #region list-persistent-subscriptions-to-stream + + var userCredentials = new UserCredentials("admin", "changeit"); + var subscriptions = await client.ListToStreamAsync( + "test-stream", + userCredentials: userCredentials + ); + + var entries = subscriptions + .Select(s => $"GroupName: {s.GroupName} EventSource: {s.EventSource} Status: {s.Status}"); + + Console.WriteLine($"Subscriptions to stream: [ {string.Join(", ", entries)} ]"); + + #endregion list-persistent-subscriptions-to-stream +} + +static async Task ListPersistentSubscriptionsToAll(EventStorePersistentSubscriptionsClient client) { + #region list-persistent-subscriptions-to-all + + var userCredentials = new UserCredentials("admin", "changeit"); + var subscriptions = await client.ListToAllAsync(userCredentials: userCredentials); + + var entries = subscriptions + .Select(s => $"GroupName: {s.GroupName} EventSource: {s.EventSource} Status: {s.Status}"); + + Console.WriteLine($"Subscriptions to all: [ {string.Join(", ", entries)} ]"); + + #endregion list-persistent-subscriptions-to-all +} + +static async Task ListAllPersistentSubscriptions(EventStorePersistentSubscriptionsClient client) { + #region list-persistent-subscriptions + + var userCredentials = new UserCredentials("admin", "changeit"); + var subscriptions = await client.ListAllAsync(userCredentials: userCredentials); + + var entries = subscriptions + .Select(s => $"GroupName: {s.GroupName} EventSource: {s.EventSource} Status: {s.Status}"); + + Console.WriteLine($"Subscriptions: [{string.Join(", ", entries)} ]"); + + #endregion list-persistent-subscriptions +} + +static async Task RestartPersistentSubscriptionSubsystem(EventStorePersistentSubscriptionsClient client) { + #region restart-persistent-subscription-subsystem + + var userCredentials = new UserCredentials("admin", "changeit"); + await client.RestartSubsystemAsync(userCredentials: userCredentials); + + Console.WriteLine("Persistent subscription subsystem restarted"); + #endregion restart-persistent-subscription-subsystem +} + +static Task HandleEvent(ResolvedEvent evnt) => Task.CompletedTask; + +class UnrecoverableException : Exception { } \ No newline at end of file diff --git a/samples/projection-management/Program.cs b/samples/projection-management/Program.cs index e864551dc..0f3d66b70 100644 --- a/samples/projection-management/Program.cs +++ b/samples/projection-management/Program.cs @@ -1,208 +1,235 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; +#pragma warning disable CS8321 // Local function is declared but never used + using System.Text.Json; -using System.Threading.Tasks; -using EventStore.Client; using Grpc.Core; -namespace projection_management { - public static class Program { - - /// - /// This sample demonstrate the projection management features. - /// It will create data in the target database. - /// It will create a series of projections with the following content - /// fromAll() .when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState(); - /// - private static async Task Main() { - - const string connection = "esdb://localhost:2113?tls=false"; - var managementClient = ManagementClient(connection); - - Console.WriteLine("Populate data"); - await Populate(connection, 100); - Console.WriteLine("RestartSubSystem"); - await RestartSubSystem(managementClient); - await Task.Delay(500); // give time to the subsystem to restart - - Console.WriteLine("Disable"); - await Disable(managementClient); - Console.WriteLine("Disable Not Found"); - await DisableNotFound(managementClient); - - Console.WriteLine("Enable"); - await Enable(managementClient); - Console.WriteLine("Enable Not Found"); - await EnableNotFound(managementClient); - - Console.WriteLine("Delete"); - await Delete(managementClient); - - Console.WriteLine("Abort"); - await Abort(managementClient); - Console.WriteLine("Abort Not Found"); - await Abort_NotFound(managementClient); - - Console.WriteLine("Reset"); - await Reset(managementClient); - Console.WriteLine("Reset Not Found"); - await Reset_NotFound(managementClient); - - Console.WriteLine("CreateContinuous"); - await CreateContinuous(managementClient); - Console.WriteLine("CreateContinuous conflict"); - await CreateContinuous_Conflict(managementClient); - //Console.WriteLine("CreateOneTime"); - //await CreateOneTime(managementClient); - Console.WriteLine("Update"); - await Update(managementClient); - Console.WriteLine("Update_NotFound"); - await Update_NotFound(managementClient); - Console.WriteLine("ListAll"); - await ListAll(managementClient); - Console.WriteLine("ListContinuous"); - await ListContinuous(managementClient); - Console.WriteLine("GetStatus"); - await GetStatus(managementClient); - // Console.WriteLine("GetState"); - // await GetState(managementClient); - Console.WriteLine("GetResult"); - await GetResult(managementClient); - } - - - - private static EventStoreProjectionManagementClient ManagementClient(string connection) { - - #region createClient - var settings = EventStoreClientSettings.Create(connection); - settings.ConnectionName = "Projection management client"; - settings.DefaultCredentials = new UserCredentials("admin", "changeit"); - var managementClient = new EventStoreProjectionManagementClient(settings); - #endregion createClient - - return managementClient; - } - private static async Task RestartSubSystem(EventStoreProjectionManagementClient managementClient) { - #region RestartSubSystem - await managementClient.RestartSubsystemAsync(); - #endregion RestartSubSystem - } - - private static async Task Disable(EventStoreProjectionManagementClient managementClient) { - #region Disable - await managementClient.DisableAsync("$by_category"); - #endregion Disable - } - - private static async Task DisableNotFound(EventStoreProjectionManagementClient managementClient) { - #region DisableNotFound - try { - await managementClient.DisableAsync("projection that does not exists"); - } catch (RpcException e) when (e.StatusCode is StatusCode.NotFound) { - Console.WriteLine(e.Message); - } catch (RpcException e) when (e.Message.Contains("NotFound")) { // will be removed in a future release - Console.WriteLine(e.Message); - } - #endregion DisableNotFound - } - - private static async Task Enable(EventStoreProjectionManagementClient managementClient) { - #region Enable - await managementClient.EnableAsync("$by_category"); - #endregion Enable - } - - private static async Task EnableNotFound(EventStoreProjectionManagementClient managementClient) { - #region EnableNotFound - try { - await managementClient.EnableAsync("projection that does not exists"); - } catch (RpcException e) when (e.StatusCode is StatusCode.NotFound) { - Console.WriteLine(e.Message); - } catch (RpcException e) when (e.Message.Contains("NotFound")) { // will be removed in a future release - Console.WriteLine(e.Message); - } - #endregion EnableNotFound - } - - private static Task Delete(EventStoreProjectionManagementClient managementClient) { - #region Delete - // this is not yet available in the .net grpc client - #endregion Delete - - return Task.CompletedTask; - } - - private static async Task Abort(EventStoreProjectionManagementClient managementClient) { - try { - var js = - "fromAll() .when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; - await managementClient.CreateContinuousAsync("countEvents_Abort", js); - } catch (RpcException e) when (e.StatusCode is StatusCode.Aborted) { - // ignore was already created in a previous run - } catch (RpcException e) when (e.Message.Contains("Conflict")) { // will be removed in a future release - // ignore was already created in a previous run - } - - #region Abort - // The .net clients prior to version 21.6 had an incorrect behavior: they will save the checkpoint. - await managementClient.AbortAsync("countEvents_Abort"); - #endregion Abort - } - - private static async Task Abort_NotFound(EventStoreProjectionManagementClient managementClient) { - #region Abort_NotFound - try { - await managementClient.AbortAsync("projection that does not exists"); - } catch (RpcException e) when (e.StatusCode is StatusCode.NotFound) { - Console.WriteLine(e.Message); - } catch (RpcException e) when (e.Message.Contains("NotFound")) { // will be removed in a future release - Console.WriteLine(e.Message); - } - #endregion Abort_NotFound - } - - private static async Task Reset(EventStoreProjectionManagementClient managementClient) { - try { - var js = - "fromAll() .when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; - await managementClient.CreateContinuousAsync("countEvents_Reset", js); - } catch (RpcException e) when (e.StatusCode is StatusCode.Internal) { - // ignore was already created in a previous run - } catch (RpcException e) when (e.Message.Contains("Conflict")) { // will be removed in a future release - // ignore was already created in a previous run - } - - #region Reset - // Checkpoint will be written prior to resetting the projection - await managementClient.ResetAsync("countEvents_Reset"); - #endregion Reset - - } - - private static async Task Reset_NotFound(EventStoreProjectionManagementClient managementClient) { - #region Reset_NotFound - try { - await managementClient.ResetAsync("projection that does not exists"); - } catch (RpcException e) when (e.StatusCode is StatusCode.NotFound) { - Console.WriteLine(e.Message); - } catch (RpcException e) when (e.Message.Contains("NotFound")) { // will be removed in a future release - Console.WriteLine(e.Message); - } - #endregion Reset_NotFound - } - - private static async Task CreateOneTime(EventStoreProjectionManagementClient managementClient) { - const string js = - "fromAll() .when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; - await managementClient.CreateOneTimeAsync(js); - } - - private static async Task CreateContinuous(EventStoreProjectionManagementClient managementClient) { - #region CreateContinuous - const string js = @"fromAll() +//// This sample demonstrate the projection management features. +//// It will create data in the target database. +//// It will create a series of projections with the following content +//// fromAll() .when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState(); + +const string connection = "esdb://localhost:2113?tls=false"; + +var managementClient = ManagementClient(connection); + +Console.WriteLine("Populate data"); +await Populate(connection, 100); +Console.WriteLine("RestartSubSystem"); +await RestartSubSystem(managementClient); +await Task.Delay(500); // give time to the subsystem to restart + +Console.WriteLine("Disable"); +await Disable(managementClient); +Console.WriteLine("Disable Not Found"); +await DisableNotFound(managementClient); + +Console.WriteLine("Enable"); +await Enable(managementClient); +Console.WriteLine("Enable Not Found"); +await EnableNotFound(managementClient); + +Console.WriteLine("Delete"); +await Delete(managementClient); + +Console.WriteLine("Abort"); +await Abort(managementClient); +Console.WriteLine("Abort Not Found"); +await Abort_NotFound(managementClient); + +Console.WriteLine("Reset"); +await Reset(managementClient); +Console.WriteLine("Reset Not Found"); +await Reset_NotFound(managementClient); + +Console.WriteLine("CreateContinuous"); +await CreateContinuous(managementClient); +Console.WriteLine("CreateContinuous conflict"); +await CreateContinuous_Conflict(managementClient); +//Console.WriteLine("CreateOneTime"); +//await CreateOneTime(managementClient); +Console.WriteLine("Update"); +await Update(managementClient); +Console.WriteLine("Update_NotFound"); +await Update_NotFound(managementClient); +Console.WriteLine("ListAll"); +await ListAll(managementClient); +Console.WriteLine("ListContinuous"); +await ListContinuous(managementClient); +Console.WriteLine("GetStatus"); +await GetStatus(managementClient); +// Console.WriteLine("GetState"); +// await GetState(managementClient); +Console.WriteLine("GetResult"); +await GetResult(managementClient); + +return; + +static EventStoreProjectionManagementClient ManagementClient(string connection) { + #region createClient + + var settings = EventStoreClientSettings.Create(connection); + settings.ConnectionName = "Projection management client"; + settings.DefaultCredentials = new UserCredentials("admin", "changeit"); + var managementClient = new EventStoreProjectionManagementClient(settings); + + #endregion createClient + + return managementClient; +} + +static async Task RestartSubSystem(EventStoreProjectionManagementClient managementClient) { + #region RestartSubSystem + + await managementClient.RestartSubsystemAsync(); + + #endregion RestartSubSystem +} + +static async Task Disable(EventStoreProjectionManagementClient managementClient) { + #region Disable + + await managementClient.DisableAsync("$by_category"); + + #endregion Disable +} + +static async Task DisableNotFound(EventStoreProjectionManagementClient managementClient) { + #region DisableNotFound + + try { + await managementClient.DisableAsync("projection that does not exists"); + } + catch (RpcException e) when (e.StatusCode is StatusCode.NotFound) { + Console.WriteLine(e.Message); + } + catch (RpcException e) when (e.Message.Contains("NotFound")) { // will be removed in a future release + Console.WriteLine(e.Message); + } + + #endregion DisableNotFound +} + +static async Task Enable(EventStoreProjectionManagementClient managementClient) { + #region Enable + + await managementClient.EnableAsync("$by_category"); + + #endregion Enable +} + +static async Task EnableNotFound(EventStoreProjectionManagementClient managementClient) { + #region EnableNotFound + + try { + await managementClient.EnableAsync("projection that does not exists"); + } + catch (RpcException e) when (e.StatusCode is StatusCode.NotFound) { + Console.WriteLine(e.Message); + } + catch (RpcException e) when (e.Message.Contains("NotFound")) { // will be removed in a future release + Console.WriteLine(e.Message); + } + + #endregion EnableNotFound +} + +static Task Delete(EventStoreProjectionManagementClient managementClient) { + #region Delete + + // this is not yet available in the .net grpc client + + #endregion Delete + + return Task.CompletedTask; +} + +static async Task Abort(EventStoreProjectionManagementClient managementClient) { + try { + var js = + "fromAll() .when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; + + await managementClient.CreateContinuousAsync("countEvents_Abort", js); + } + catch (RpcException e) when (e.StatusCode is StatusCode.Aborted) { + // ignore was already created in a previous run + } + catch (RpcException e) when (e.Message.Contains("Conflict")) { // will be removed in a future release + // ignore was already created in a previous run + } + + #region Abort + + // The .net clients prior to version 21.6 had an incorrect behavior: they will save the checkpoint. + await managementClient.AbortAsync("countEvents_Abort"); + + #endregion Abort +} + +static async Task Abort_NotFound(EventStoreProjectionManagementClient managementClient) { + #region Abort_NotFound + + try { + await managementClient.AbortAsync("projection that does not exists"); + } + catch (RpcException e) when (e.StatusCode is StatusCode.NotFound) { + Console.WriteLine(e.Message); + } + catch (RpcException e) when (e.Message.Contains("NotFound")) { // will be removed in a future release + Console.WriteLine(e.Message); + } + + #endregion Abort_NotFound +} + +static async Task Reset(EventStoreProjectionManagementClient managementClient) { + try { + var js = + "fromAll() .when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; + + await managementClient.CreateContinuousAsync("countEvents_Reset", js); + } + catch (RpcException e) when (e.StatusCode is StatusCode.Internal) { + // ignore was already created in a previous run + } + catch (RpcException e) when (e.Message.Contains("Conflict")) { // will be removed in a future release + // ignore was already created in a previous run + } + + #region Reset + + // Checkpoint will be written prior to resetting the projection + await managementClient.ResetAsync("countEvents_Reset"); + + #endregion Reset +} + +static async Task Reset_NotFound(EventStoreProjectionManagementClient managementClient) { + #region Reset_NotFound + + try { + await managementClient.ResetAsync("projection that does not exists"); + } + catch (RpcException e) when (e.StatusCode is StatusCode.NotFound) { + Console.WriteLine(e.Message); + } + catch (RpcException e) when (e.Message.Contains("NotFound")) { // will be removed in a future release + Console.WriteLine(e.Message); + } + + #endregion Reset_NotFound +} + +static async Task CreateOneTime(EventStoreProjectionManagementClient managementClient) { + const string js = + "fromAll() .when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; + + await managementClient.CreateOneTimeAsync(js); +} + +static async Task CreateContinuous(EventStoreProjectionManagementClient managementClient) { + #region CreateContinuous + + const string js = @"fromAll() .when({ $init: function() { return { @@ -214,14 +241,15 @@ private static async Task CreateContinuous(EventStoreProjectionManagementClient } }) .outputState();"; - var name = $"countEvents_Create_{Guid.NewGuid()}"; - await managementClient.CreateContinuousAsync(name, js); - #endregion CreateContinuous - } - private static async Task CreateContinuous_Conflict(EventStoreProjectionManagementClient managementClient) { + var name = $"countEvents_Create_{Guid.NewGuid()}"; + await managementClient.CreateContinuousAsync(name, js); - const string js = @"fromAll() + #endregion CreateContinuous +} + +static async Task CreateContinuous_Conflict(EventStoreProjectionManagementClient managementClient) { + const string js = @"fromAll() .when({ $init: function() { return { @@ -233,25 +261,30 @@ private static async Task CreateContinuous_Conflict(EventStoreProjectionManageme } }) .outputState();"; - var name = $"countEvents_Create_{Guid.NewGuid()}"; - - #region CreateContinuous_Conflict - await managementClient.CreateContinuousAsync(name, js); - try { - - await managementClient.CreateContinuousAsync(name, js); - } catch (RpcException e) when (e.StatusCode is StatusCode.AlreadyExists) { - Console.WriteLine(e.Message); - } catch (RpcException e) when (e.Message.Contains("Conflict")) { // will be removed in a future release - var format = $"{name} already exists"; - Console.WriteLine(format); - } - #endregion CreateContinuous_Conflict - } - - private static async Task Update(EventStoreProjectionManagementClient managementClient) { - #region Update - const string js = @"fromAll() + + var name = $"countEvents_Create_{Guid.NewGuid()}"; + + #region CreateContinuous_Conflict + + await managementClient.CreateContinuousAsync(name, js); + try { + await managementClient.CreateContinuousAsync(name, js); + } + catch (RpcException e) when (e.StatusCode is StatusCode.AlreadyExists) { + Console.WriteLine(e.Message); + } + catch (RpcException e) when (e.Message.Contains("Conflict")) { // will be removed in a future release + var format = $"{name} already exists"; + Console.WriteLine(format); + } + + #endregion CreateContinuous_Conflict +} + +static async Task Update(EventStoreProjectionManagementClient managementClient) { + #region Update + + const string js = @"fromAll() .when({ $init: function() { return { @@ -263,92 +296,107 @@ private static async Task Update(EventStoreProjectionManagementClient management } }) .outputState();"; - var name = $"countEvents_Update_{Guid.NewGuid()}"; - - await managementClient.CreateContinuousAsync(name, "fromAll().when()"); - await managementClient.UpdateAsync(name, js); - #endregion Update - } - - private static async Task Update_NotFound(EventStoreProjectionManagementClient managementClient) { - #region Update_NotFound - try { - await managementClient.UpdateAsync("Update Not existing projection", "fromAll().when()"); - } catch (RpcException e) when (e.StatusCode is StatusCode.NotFound) { - Console.WriteLine(e.Message); - } catch (RpcException e) when (e.Message.Contains("NotFound")) { // will be removed in a future release - Console.WriteLine("'Update Not existing projection' does not exists and can not be updated"); - } - #endregion Update_NotFound - } - - private static async Task ListAll(EventStoreProjectionManagementClient managementClient) { - #region ListAll - var details = managementClient.ListAllAsync(); - await foreach (var detail in details) { - Console.WriteLine( - $@"{detail.Name}, {detail.Status}, {detail.CheckpointStatus}, {detail.Mode}, {detail.Progress}"); - } - #endregion ListAll - } - - private static async Task ListContinuous(EventStoreProjectionManagementClient managementClient) { - #region ListContinuous - var details = managementClient.ListContinuousAsync(); - await foreach (var detail in details) { - Console.WriteLine( - $@"{detail.Name}, {detail.Status}, {detail.CheckpointStatus}, {detail.Mode}, {detail.Progress}"); - } - #endregion ListContinuous - - } - - private static async Task GetStatus(EventStoreProjectionManagementClient managementClient) { - const string js = - "fromAll().when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; - var name = $"countEvents_status_{Guid.NewGuid()}"; - - #region GetStatus - await managementClient.CreateContinuousAsync(name, js); - var status = await managementClient.GetStatusAsync(name); - Console.WriteLine( - $@"{status?.Name}, {status?.Status}, {status?.CheckpointStatus}, {status?.Mode}, {status?.Progress}"); - #endregion GetStatus - } - - private static async Task GetState(EventStoreProjectionManagementClient managementClient) { - - // will have to wait for the client to be fixed before we import in the doc - - #region GetState - const string js = - "fromAll().when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; - var name = $"countEvents_State_{Guid.NewGuid()}"; - - await managementClient.CreateContinuousAsync(name, js); - //give it some time to process and have a state. - await Task.Delay(500); - - var stateDocument = await managementClient.GetStateAsync(name); - var result = await managementClient.GetStateAsync(name); - - Console.WriteLine(DocToString(stateDocument)); - Console.WriteLine(result); - - static async Task DocToString(JsonDocument d) { - await using var stream = new MemoryStream(); - Utf8JsonWriter writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = false }); - d.WriteTo(writer); - await writer.FlushAsync(); - return Encoding.UTF8.GetString(stream.ToArray()); - } - #endregion GetState - } - - private static async Task GetResult(EventStoreProjectionManagementClient managementClient) { - - #region GetResult - const string js = @"fromAll() + + var name = $"countEvents_Update_{Guid.NewGuid()}"; + + await managementClient.CreateContinuousAsync(name, "fromAll().when()"); + await managementClient.UpdateAsync(name, js); + + #endregion Update +} + +static async Task Update_NotFound(EventStoreProjectionManagementClient managementClient) { + #region Update_NotFound + + try { + await managementClient.UpdateAsync("Update Not existing projection", "fromAll().when()"); + } + catch (RpcException e) when (e.StatusCode is StatusCode.NotFound) { + Console.WriteLine(e.Message); + } + catch (RpcException e) when (e.Message.Contains("NotFound")) { // will be removed in a future release + Console.WriteLine("'Update Not existing projection' does not exists and can not be updated"); + } + + #endregion Update_NotFound +} + +static async Task ListAll(EventStoreProjectionManagementClient managementClient) { + #region ListAll + + var details = managementClient.ListAllAsync(); + await foreach (var detail in details) + Console.WriteLine( + $@"{detail.Name}, {detail.Status}, {detail.CheckpointStatus}, {detail.Mode}, {detail.Progress}" + ); + + #endregion ListAll +} + +static async Task ListContinuous(EventStoreProjectionManagementClient managementClient) { + #region ListContinuous + + var details = managementClient.ListContinuousAsync(); + await foreach (var detail in details) + Console.WriteLine( + $@"{detail.Name}, {detail.Status}, {detail.CheckpointStatus}, {detail.Mode}, {detail.Progress}" + ); + + #endregion ListContinuous +} + +static async Task GetStatus(EventStoreProjectionManagementClient managementClient) { + const string js = + "fromAll().when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; + + var name = $"countEvents_status_{Guid.NewGuid()}"; + + #region GetStatus + + await managementClient.CreateContinuousAsync(name, js); + var status = await managementClient.GetStatusAsync(name); + Console.WriteLine( + $@"{status?.Name}, {status?.Status}, {status?.CheckpointStatus}, {status?.Mode}, {status?.Progress}" + ); + + #endregion GetStatus +} + +static async Task GetState(EventStoreProjectionManagementClient managementClient) { + // will have to wait for the client to be fixed before we import in the doc + + #region GetState + + const string js = + "fromAll().when({$init:function(){return {count:0};},$any:function(s, e){s.count += 1;}}).outputState();"; + + var name = $"countEvents_State_{Guid.NewGuid()}"; + + await managementClient.CreateContinuousAsync(name, js); + //give it some time to process and have a state. + await Task.Delay(500); + + var stateDocument = await managementClient.GetStateAsync(name); + var result = await managementClient.GetStateAsync(name); + + Console.WriteLine(DocToString(stateDocument)); + Console.WriteLine(result); + + static async Task DocToString(JsonDocument d) { + await using var stream = new MemoryStream(); + var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = false }); + d.WriteTo(writer); + await writer.FlushAsync(); + return Encoding.UTF8.GetString(stream.ToArray()); + } + + #endregion GetState +} + +static async Task GetResult(EventStoreProjectionManagementClient managementClient) { + #region GetResult + + const string js = @"fromAll() .when({ $init: function() { return { @@ -360,43 +408,48 @@ private static async Task GetResult(EventStoreProjectionManagementClient managem } }) .outputState();"; - var name = $"countEvents_Result_{Guid.NewGuid()}"; - - await managementClient.CreateContinuousAsync(name, js); - await Task.Delay(500); //give it some time to have a result. - - // Results are retrieved either as JsonDocument or a typed result - var document = await managementClient.GetResultAsync(name); - var result = await managementClient.GetResultAsync(name); - - Console.WriteLine(DocToString(document)); - Console.WriteLine(result); - - static string DocToString(JsonDocument d) { - using var stream = new MemoryStream(); - using Utf8JsonWriter writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = false }); - d.WriteTo(writer); - writer.Flush(); - return Encoding.UTF8.GetString(stream.ToArray()); - } - #endregion GetResult - } - - public class Result { - public int count { get; set; } - public override string ToString() => $"count= {count}"; - }; - - private static async Task Populate(string connection, int numberOfEvents) { - var settings = EventStoreClientSettings.Create(connection); - settings.DefaultCredentials = new UserCredentials("admin", "changeit"); - var client = new EventStoreClient(settings); - var messages = Enumerable.Range(0, numberOfEvents).Select(number => - new EventData(Uuid.NewUuid(), - "eventtype", - Encoding.UTF8.GetBytes($@"{{ ""Id"":{number} }}")) - ); - await client.AppendToStreamAsync("sample", StreamState.Any, messages); - } + + var name = $"countEvents_Result_{Guid.NewGuid()}"; + + await managementClient.CreateContinuousAsync(name, js); + await Task.Delay(500); //give it some time to have a result. + + // Results are retrieved either as JsonDocument or a typed result + var document = await managementClient.GetResultAsync(name); + var result = await managementClient.GetResultAsync(name); + + Console.WriteLine(DocToString(document)); + Console.WriteLine(result); + + static string DocToString(JsonDocument d) { + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = false }); + d.WriteTo(writer); + writer.Flush(); + return Encoding.UTF8.GetString(stream.ToArray()); } + + #endregion GetResult } + +static async Task Populate(string connection, int numberOfEvents) { + var settings = EventStoreClientSettings.Create(connection); + settings.DefaultCredentials = new UserCredentials("admin", "changeit"); + var client = new EventStoreClient(settings); + var messages = Enumerable.Range(0, numberOfEvents).Select( + number => + new EventData( + Uuid.NewUuid(), + "eventtype", + Encoding.UTF8.GetBytes($@"{{ ""Id"":{number} }}") + ) + ); + + await client.AppendToStreamAsync("sample", StreamState.Any, messages); +} + +public class Result { + public int count { get; set; } + + public override string ToString() => $"count= {count}"; +}; \ No newline at end of file diff --git a/samples/quick-start/Program.cs b/samples/quick-start/Program.cs index e79b524d9..0cd070137 100644 --- a/samples/quick-start/Program.cs +++ b/samples/quick-start/Program.cs @@ -1,77 +1,70 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client; - -namespace quick_start { - public class TestEvent { - public string? EntityId { get; set; } - public string? ImportantData { get; set; } - } - - class Program { - static void Main(string[] args) { - } - - static async Task Samples() { - CancellationTokenSource tokenSource = new CancellationTokenSource(); - CancellationToken cancellationToken = tokenSource.Token; - - #region createClient - var settings = EventStoreClientSettings - .Create("{connectionString}"); - var client = new EventStoreClient(settings); - #endregion createClient - - #region createEvent - var evt = new TestEvent - { - EntityId = Guid.NewGuid().ToString("N"), - ImportantData = "I wrote my first event!" - }; - - var eventData = new EventData( - Uuid.NewUuid(), - "TestEvent", - JsonSerializer.SerializeToUtf8Bytes(evt) - ); - #endregion createEvent - - #region appendEvents - await client.AppendToStreamAsync( - "some-stream", - StreamState.Any, - new[] { eventData }, - cancellationToken: cancellationToken - ); - #endregion appendEvents - - #region overriding-user-credentials - await client.AppendToStreamAsync( - "some-stream", - StreamState.Any, - new[] { eventData }, - userCredentials: new UserCredentials("admin", "changeit"), - cancellationToken: cancellationToken - ); - #endregion overriding-user-credentials - - #region readStream - var result = client.ReadStreamAsync( - Direction.Forwards, - "some-stream", - StreamPosition.Start, - cancellationToken: cancellationToken); - - var events = await result.ToListAsync(cancellationToken); - #endregion readStream - } - } -} +using System.Text.Json; + +var tokenSource = new CancellationTokenSource(); +var cancellationToken = tokenSource.Token; + +#region createClient + +const string connectionString = "esdb://admin:changeit@localhost:2113?tls=false&tlsVerifyCert=false"; + +var settings = EventStoreClientSettings.Create(connectionString); + +var client = new EventStoreClient(settings); + +#endregion createClient + +#region createEvent + +var evt = new TestEvent { + EntityId = Guid.NewGuid().ToString("N"), + ImportantData = "I wrote my first event!" +}; + +var eventData = new EventData( + Uuid.NewUuid(), + "TestEvent", + JsonSerializer.SerializeToUtf8Bytes(evt) +); + +#endregion createEvent + +#region appendEvents + +await client.AppendToStreamAsync( + "some-stream", + StreamState.Any, + new[] { eventData }, + cancellationToken: cancellationToken +); + +#endregion appendEvents + +#region overriding-user-credentials + +await client.AppendToStreamAsync( + "some-stream", + StreamState.Any, + new[] { eventData }, + userCredentials: new UserCredentials("admin", "changeit"), + cancellationToken: cancellationToken +); + +#endregion overriding-user-credentials + +#region readStream + +var result = client.ReadStreamAsync( + Direction.Forwards, + "some-stream", + StreamPosition.Start, + cancellationToken: cancellationToken +); + +var events = await result.ToListAsync(cancellationToken); + +#endregion readStream + +public class TestEvent { + public string? EntityId { get; set; } + public string? ImportantData { get; set; } +} \ No newline at end of file diff --git a/samples/quick-start/docker-compose.yml b/samples/quick-start/docker-compose.yml index 9001ea312..f6621fd78 100644 --- a/samples/quick-start/docker-compose.yml +++ b/samples/quick-start/docker-compose.yml @@ -1,9 +1,12 @@ -version: '3' +version: '3.5' services: eventstore: - image: eventstore/eventstore:20.10.0-buster-slim + image: eventstore/eventstore:latest environment: - - EVENTSTORE_INSECURE=true - - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true + EVENTSTORE_INSECURE: true + EVENTSTORE_MEM_DB: false + EVENTSTORE_RUN_PROJECTIONS: all + EVENTSTORE_START_STANDARD_PROJECTIONS: true + EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP: true ports: - - 2113:2113 + - "2113:2113" \ No newline at end of file diff --git a/samples/quick-start/quick-start.csproj b/samples/quick-start/quick-start.csproj index 9921046d6..b33948026 100644 --- a/samples/quick-start/quick-start.csproj +++ b/samples/quick-start/quick-start.csproj @@ -5,7 +5,7 @@ - + diff --git a/samples/reading-events/Program.cs b/samples/reading-events/Program.cs index c09c64238..854634da8 100644 --- a/samples/reading-events/Program.cs +++ b/samples/reading-events/Program.cs @@ -1,295 +1,321 @@ -using System; -using System.Linq; -using System.Net.Http; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client; - -namespace reading_events { - class Program { - static async Task Main(string[] args) { - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://localhost:2113?tls=false") - ); - - var events = Enumerable.Range(0, 20) - .Select(r => new EventData( - Uuid.NewUuid(), - "some-event", - Encoding.UTF8.GetBytes("{\"id\": \"" + r + "\" \"value\": \"some value\"}"))); - - await client.AppendToStreamAsync( - "some-stream", - StreamState.Any, - events); - - await ReadFromStream(client); - - return; - } +#pragma warning disable CS8321 // Local function is declared but never used - private static async Task ReadFromStream(EventStoreClient client) { - #region read-from-stream - var events = client.ReadStreamAsync( - Direction.Forwards, - "some-stream", - StreamPosition.Start); - #endregion read-from-stream - - #region iterate-stream - await foreach (var @event in events) { - Console.WriteLine(Encoding.UTF8.GetString(@event.Event.Data.ToArray())); - } - #endregion iterate-stream - - #region #read-from-stream-positions - Console.WriteLine(events.FirstStreamPosition); - Console.WriteLine(events.LastStreamPosition); - #endregion - } +await using var client = new EventStoreClient(EventStoreClientSettings.Create("esdb://localhost:2113?tls=false")); - private static async Task ReadFromStreamMessages(EventStoreClient client) { - #region read-from-stream-messages - var streamPosition = StreamPosition.Start; - var results = client.ReadStreamAsync( - Direction.Forwards, - "some-stream", - streamPosition); - #endregion read-from-stream-messages - - #region iterate-stream-messages - await foreach (var message in results.Messages) { - switch (message) { - case StreamMessage.Ok ok: - Console.WriteLine("Stream found."); - break; - case StreamMessage.NotFound: - Console.WriteLine("Stream not found."); - return; - case StreamMessage.Event(var resolvedEvent): - Console.WriteLine(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span)); - break; - case StreamMessage.FirstStreamPosition(var sp): - Console.WriteLine($"{sp} is after {streamPosition}; updating checkpoint."); - streamPosition = sp; - break; - case StreamMessage.LastStreamPosition(var sp): - Console.WriteLine($"The end of the stream is {sp}"); - break; - default: - break; - } - } - #endregion iterate-stream-messages - } +var events = Enumerable.Range(0, 20) + .Select( + r => new EventData( + Uuid.NewUuid(), + "some-event", + Encoding.UTF8.GetBytes($"{{\"id\": \"{r}\" \"value\": \"some value\"}}") + ) + ); - private static async Task ReadFromStreamPosition(EventStoreClient client) { - #region read-from-stream-position - var events = client.ReadStreamAsync( - Direction.Forwards, - "some-stream", - revision: 10, - maxCount: 20); - #endregion read-from-stream-position - - #region iterate-stream - await foreach (var @event in events) { - Console.WriteLine(Encoding.UTF8.GetString(@event.Event.Data.ToArray())); - } - #endregion iterate-stream - } +await client.AppendToStreamAsync( + "some-stream", + StreamState.Any, + events +); + +await ReadFromStream(client); + +return; + +static async Task ReadFromStream(EventStoreClient client) { + #region read-from-stream + + var events = client.ReadStreamAsync( + Direction.Forwards, + "some-stream", + StreamPosition.Start + ); + + #endregion read-from-stream + + #region iterate-stream + + await foreach (var @event in events) Console.WriteLine(Encoding.UTF8.GetString(@event.Event.Data.ToArray())); + + #endregion iterate-stream + + #region #read-from-stream-positions - private static async Task ReadFromStreamPositionCheck(EventStoreClient client) { - #region checking-for-stream-presence - var result = client.ReadStreamAsync( - Direction.Forwards, - "some-stream", - revision: 10, - maxCount: 20); + Console.WriteLine(events.FirstStreamPosition); + Console.WriteLine(events.LastStreamPosition); - if (await result.ReadState == ReadState.StreamNotFound) { + #endregion +} + +static async Task ReadFromStreamMessages(EventStoreClient client) { + #region read-from-stream-messages + + var streamPosition = StreamPosition.Start; + var results = client.ReadStreamAsync( + Direction.Forwards, + "some-stream", + streamPosition + ); + + #endregion read-from-stream-messages + + #region iterate-stream-messages + + await foreach (var message in results.Messages) + switch (message) { + case StreamMessage.Ok ok: + Console.WriteLine("Stream found."); + break; + + case StreamMessage.NotFound: + Console.WriteLine("Stream not found."); return; - } - await foreach (var e in result) { - Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); - } - #endregion checking-for-stream-presence - } + case StreamMessage.Event(var resolvedEvent): + Console.WriteLine(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span)); + break; - private static async Task ReadFromStreamBackwards(EventStoreClient client) { - #region reading-backwards - var events = client.ReadStreamAsync( - Direction.Backwards, - "some-stream", - StreamPosition.End); - - await foreach (var e in events) { - Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); - } - #endregion reading-backwards - } + case StreamMessage.FirstStreamPosition(var sp): + Console.WriteLine($"{sp} is after {streamPosition}; updating checkpoint."); + streamPosition = sp; + break; - private static async Task ReadFromStreamMessagesBackwards(EventStoreClient client) { - #region read-from-stream-messages-backwards - var results = client.ReadStreamAsync( - Direction.Forwards, - "some-stream", - StreamPosition.End); - #endregion read-from-stream-messages-backwards - - #region iterate-stream-messages-backwards - await foreach (var message in results.Messages) { - switch (message) { - case StreamMessage.Ok ok: - Console.WriteLine("Stream found."); - break; - case StreamMessage.NotFound: - Console.WriteLine("Stream not found."); - return; - case StreamMessage.Event(var resolvedEvent): - Console.WriteLine(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span)); - break; - case StreamMessage.LastStreamPosition(var sp): - Console.WriteLine($"The end of the stream is {sp}"); - break; - } - } - #endregion iterate-stream-messages-backwards + case StreamMessage.LastStreamPosition(var sp): + Console.WriteLine($"The end of the stream is {sp}"); + break; + + default: + break; } + #endregion iterate-stream-messages +} - private static async Task ReadFromAllStream(EventStoreClient client) { - #region read-from-all-stream - var events = client.ReadAllAsync( - Direction.Forwards, Position.Start); - #endregion read-from-all-stream +static async Task ReadFromStreamPosition(EventStoreClient client) { + #region read-from-stream-position - #region read-from-all-stream-iterate - await foreach (var e in events) { - Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); - } - #endregion read-from-all-stream-iterate - } + var events = client.ReadStreamAsync( + Direction.Forwards, + "some-stream", + 10, + 20 + ); - private static async Task ReadFromAllStreamMessages(EventStoreClient client) { - #region read-from-all-stream-messages - var position = Position.Start; - var results = client.ReadAllAsync( - Direction.Forwards, - position: position); - #endregion read-from-all-stream-messages - - #region iterate-all-stream-messages - await foreach (var message in results.Messages) { - switch (message) { - case StreamMessage.Event(var resolvedEvent): - Console.WriteLine(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span)); - break; - case StreamMessage.LastAllStreamPosition(var p): - Console.WriteLine($"The end of the $all stream is {p}"); - break; - } - } - #endregion iterate-all-stream-messages - } + #endregion read-from-stream-position - private static async Task IgnoreSystemEvents(EventStoreClient client) { - #region ignore-system-events - var events = client.ReadAllAsync( - Direction.Forwards, Position.Start); + #region iterate-stream - await foreach (var e in events) { - if (e.Event.EventType.StartsWith("$")) { - continue; - } + await foreach (var @event in events) Console.WriteLine(Encoding.UTF8.GetString(@event.Event.Data.ToArray())); - Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); - } - #endregion ignore-system-events - } + #endregion iterate-stream +} - private static async Task ReadFromAllStreamBackwards(EventStoreClient client) { - #region read-from-all-stream-backwards - var events = client.ReadAllAsync( - Direction.Backwards, Position.End); - #endregion read-from-all-stream-backwards - - #region read-from-all-stream-iterate - await foreach (var e in events) { - Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); - } - #endregion read-from-all-stream-iterate - } +static async Task ReadFromStreamPositionCheck(EventStoreClient client) { + #region checking-for-stream-presence - private static async Task ReadFromAllStreamBackwardsMessages(EventStoreClient client) { - #region read-from-all-stream-messages-backwards - var position = Position.End; - var results = client.ReadAllAsync( - Direction.Backwards, - position: position); - #endregion read-from-all-stream-messages-backwards - - #region iterate-all-stream-messages-backwards - await foreach (var message in results.Messages) { - switch (message) { - case StreamMessage.Event(var resolvedEvent): - Console.WriteLine(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span)); - break; - case StreamMessage.LastAllStreamPosition(var p): - Console.WriteLine($"{p} is before {position}; updating checkpoint."); - position = p; - break; - } - } - #endregion iterate-all-stream-messages-backwards - } + var result = client.ReadStreamAsync( + Direction.Forwards, + "some-stream", + 10, + 20 + ); - private static async Task FilteringOutSystemEvents(EventStoreClient client) { - var events = client.ReadAllAsync(Direction.Forwards, Position.Start); + if (await result.ReadState == ReadState.StreamNotFound) return; - await foreach (var e in events) { - if (!e.Event.EventType.StartsWith("$")) { - Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); - } - } - } + await foreach (var e in result) Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); - private void ReadStreamOverridingUserCredentials(EventStoreClient client, CancellationToken cancellationToken) - { - #region overriding-user-credentials - var result = client.ReadStreamAsync( - Direction.Forwards, - "some-stream", - StreamPosition.Start, - userCredentials: new UserCredentials("admin", "changeit"), - cancellationToken: cancellationToken); - #endregion overriding-user-credentials - } + #endregion checking-for-stream-presence +} + +static async Task ReadFromStreamBackwards(EventStoreClient client) { + #region reading-backwards + + var events = client.ReadStreamAsync( + Direction.Backwards, + "some-stream", + StreamPosition.End + ); + + await foreach (var e in events) Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); + + #endregion reading-backwards +} + +static async Task ReadFromStreamMessagesBackwards(EventStoreClient client) { + #region read-from-stream-messages-backwards + + var results = client.ReadStreamAsync( + Direction.Forwards, + "some-stream", + StreamPosition.End + ); + + #endregion read-from-stream-messages-backwards + + #region iterate-stream-messages-backwards + + await foreach (var message in results.Messages) + switch (message) { + case StreamMessage.Ok ok: + Console.WriteLine("Stream found."); + break; - private void ReadAllOverridingUserCredentials(EventStoreClient client, CancellationToken cancellationToken) - { - #region read-all-overriding-user-credentials - var result = client.ReadAllAsync( - Direction.Forwards, - Position.Start, - userCredentials: new UserCredentials("admin", "changeit"), - cancellationToken: cancellationToken); - #endregion read-all-overriding-user-credentials + case StreamMessage.NotFound: + Console.WriteLine("Stream not found."); + return; + + case StreamMessage.Event(var resolvedEvent): + Console.WriteLine(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span)); + break; + + case StreamMessage.LastStreamPosition(var sp): + Console.WriteLine($"The end of the stream is {sp}"); + break; } - - - private void ReadAllResolvingLinkTos(EventStoreClient client, CancellationToken cancellationToken) - { - #region read-from-all-stream-resolving-link-Tos - var result = client.ReadAllAsync( - Direction.Forwards, - Position.Start, - resolveLinkTos: true, - cancellationToken: cancellationToken); - #endregion read-from-all-stream-resolving-link-Tos + + #endregion iterate-stream-messages-backwards +} + +static async Task ReadFromAllStream(EventStoreClient client) { + #region read-from-all-stream + + var events = client.ReadAllAsync(Direction.Forwards, Position.Start); + + #endregion read-from-all-stream + + #region read-from-all-stream-iterate + + await foreach (var e in events) Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); + + #endregion read-from-all-stream-iterate +} + +static async Task ReadFromAllStreamMessages(EventStoreClient client) { + #region read-from-all-stream-messages + + var position = Position.Start; + var results = client.ReadAllAsync( + Direction.Forwards, + position + ); + + #endregion read-from-all-stream-messages + + #region iterate-all-stream-messages + + await foreach (var message in results.Messages) + switch (message) { + case StreamMessage.Event(var resolvedEvent): + Console.WriteLine(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span)); + break; + + case StreamMessage.LastAllStreamPosition(var p): + Console.WriteLine($"The end of the $all stream is {p}"); + break; } + + #endregion iterate-all-stream-messages +} + +static async Task IgnoreSystemEvents(EventStoreClient client) { + #region ignore-system-events + + var events = client.ReadAllAsync(Direction.Forwards, Position.Start); + + await foreach (var e in events) { + if (e.Event.EventType.StartsWith("$")) continue; + + Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); } + + #endregion ignore-system-events } + +static async Task ReadFromAllStreamBackwards(EventStoreClient client) { + #region read-from-all-stream-backwards + + var events = client.ReadAllAsync(Direction.Backwards, Position.End); + + #endregion read-from-all-stream-backwards + + #region read-from-all-stream-iterate + + await foreach (var e in events) Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); + + #endregion read-from-all-stream-iterate +} + +static async Task ReadFromAllStreamBackwardsMessages(EventStoreClient client) { + #region read-from-all-stream-messages-backwards + + var position = Position.End; + var results = client.ReadAllAsync( + Direction.Backwards, + position + ); + + #endregion read-from-all-stream-messages-backwards + + #region iterate-all-stream-messages-backwards + + await foreach (var message in results.Messages) + switch (message) { + case StreamMessage.Event(var resolvedEvent): + Console.WriteLine(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span)); + break; + + case StreamMessage.LastAllStreamPosition(var p): + Console.WriteLine($"{p} is before {position}; updating checkpoint."); + position = p; + break; + } + + #endregion iterate-all-stream-messages-backwards +} + +static async Task FilteringOutSystemEvents(EventStoreClient client) { + var events = client.ReadAllAsync(Direction.Forwards, Position.Start); + + await foreach (var e in events) + if (!e.Event.EventType.StartsWith("$")) + Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); +} + +static void ReadStreamOverridingUserCredentials(EventStoreClient client, CancellationToken cancellationToken) { + #region overriding-user-credentials + + var result = client.ReadStreamAsync( + Direction.Forwards, + "some-stream", + StreamPosition.Start, + userCredentials: new UserCredentials("admin", "changeit"), + cancellationToken: cancellationToken + ); + + #endregion overriding-user-credentials +} + +static void ReadAllOverridingUserCredentials(EventStoreClient client, CancellationToken cancellationToken) { + #region read-all-overriding-user-credentials + + var result = client.ReadAllAsync( + Direction.Forwards, + Position.Start, + userCredentials: new UserCredentials("admin", "changeit"), + cancellationToken: cancellationToken + ); + + #endregion read-all-overriding-user-credentials +} + +static void ReadAllResolvingLinkTos(EventStoreClient client, CancellationToken cancellationToken) { + #region read-from-all-stream-resolving-link-Tos + + var result = client.ReadAllAsync( + Direction.Forwards, + Position.Start, + resolveLinkTos: true, + cancellationToken: cancellationToken + ); + + #endregion read-from-all-stream-resolving-link-Tos +} \ No newline at end of file diff --git a/samples/reading-events/reading-events.csproj b/samples/reading-events/reading-events.csproj index 0ae95a9a5..436254ea7 100644 --- a/samples/reading-events/reading-events.csproj +++ b/samples/reading-events/reading-events.csproj @@ -5,7 +5,7 @@ - + \ No newline at end of file diff --git a/samples/secure-with-tls/.dockerignore b/samples/secure-with-tls/.dockerignore index cf69769bd..cd967fc3a 100644 --- a/samples/secure-with-tls/.dockerignore +++ b/samples/secure-with-tls/.dockerignore @@ -1,4 +1,25 @@ -**/bin/ -**/obj/ -**/out/ -**/TestResults/ \ No newline at end of file +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/samples/secure-with-tls/Dockerfile b/samples/secure-with-tls/Dockerfile index 5dc195f0d..6e454b881 100644 --- a/samples/secure-with-tls/Dockerfile +++ b/samples/secure-with-tls/Dockerfile @@ -1,37 +1,72 @@ -# BUILDER -FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS builder +ARG BUILDER_IMG=mcr.microsoft.com/dotnet/sdk:8.0-jammy +ARG RUNNER_IMG=mcr.microsoft.com/dotnet/runtime:8.0.0-jammy +ARG APP_NAME="secure-with-tls" +ARG BUILD_CONFIGURATION=Release +#--------------------------------------------- +# Prepare runtime image +#--------------------------------------------- +FROM $RUNNER_IMG AS run +ARG APP_NAME + +# copy pregenerated certificates into usr local CA location +COPY ./certs/ca/ca.crt /usr/local/share/ca-certificates/eventstoredb_ca.crt + +# set permissions +RUN chmod 644 /usr/local/share/ca-certificates/eventstoredb_ca.crt + +# install certificates +RUN update-ca-certificates + +# configure entrypoint WORKDIR /app +ENV ENTRYPOINT "dotnet ${APP_NAME}.dll" +ENTRYPOINT $ENTRYPOINT -# Copy csproj and restore -COPY ./secure-with-tls.csproj ./ +#--------------------------------------------- +# Build application +#--------------------------------------------- +FROM $BUILDER_IMG AS build +ARG APP_NAME +ARG BUILD_CONFIGURATION + +WORKDIR /src + +# copy project file +COPY ${APP_NAME}.csproj /src/${APP_NAME}/ + +# helper var +ENV PROJECT_FILE_PATH=$APP_NAME/$APP_NAME.csproj # we need comment out project reference and uncomment the package reference -# If you're using the package reference those lines are not needed -RUN sed -i 's///' secure-with-tls.csproj && \ - sed -i 's////' $PROJECT_FILE_PATH && \ + sed -i 's// { eventData } + ); + + Console.WriteLine($"SUCCESS! Append result: {appendResult.LogPosition}"); +} +catch (Exception exception) { + const string noNodeConnectionErrorMessage = "No connection could be made because the target machine actively refused it."; + const string connectionRefused = "Connection refused"; + const string certificateIsNotInstalledOrInvalidErrorMessage = "The remote certificate is invalid according to the validation procedure."; + var innerException = exception.InnerException; + + if (innerException is RpcException rpcException) { + if (rpcException.Message.Contains(noNodeConnectionErrorMessage) + || rpcException.Message.Contains(connectionRefused)) { + Console.WriteLine( + $"FAILED! {noNodeConnectionErrorMessage} " + + $"Please makes sure that: EventStoreDB node is running, you're using a valid IP " + + $"address or DNS name, that port is valid and exposed (forwarded) in node config." + ); - try { - var appendResult = await client.AppendToStreamAsync( - "some-stream", - StreamState.Any, - new List { - eventData - }); - Console.WriteLine($"SUCCESS! Append result: {appendResult.LogPosition}"); - } - catch (Exception exception) - { - var innerException = exception.InnerException; + return; + } - if (innerException is RpcException rpcException) - { - if (rpcException.Message.Contains(NoNodeConnectionErrorMessage) || rpcException.Message.Contains(ConnectionRefused)) { - Console.WriteLine( - $"FAILED! {NoNodeConnectionErrorMessage} Please makes sure that: EventStoreDB node is running, you're using a valid IP address or DNS name, that port is valid and exposed (forwarded) in node config."); - return; - } + if (rpcException.Message.Contains(certificateIsNotInstalledOrInvalidErrorMessage)) { + Console.WriteLine( + $"FAILED! {certificateIsNotInstalledOrInvalidErrorMessage} " + + $"Please makes sure that you installed CA certificate on client environment " + + $"and that it was generated with IP address or DNS name used for connecting." + ); - if (rpcException.Message.Contains(CertificateIsNotInstalledOrInvalidErrorMessage)) - { - Console.WriteLine( - $"FAILED! {CertificateIsNotInstalledOrInvalidErrorMessage} Please makes sure that you installed CA certificate on client environment and that it was generated with IP address or DNS name used for connecting."); - return; - } - } - Console.WriteLine($"FAILED! {exception}"); - } + return; } } -} + + Console.WriteLine($"FAILED! {exception}"); +} \ No newline at end of file diff --git a/samples/secure-with-tls/README.md b/samples/secure-with-tls/README.md index 01f7f96d4..74da47d62 100644 --- a/samples/secure-with-tls/README.md +++ b/samples/secure-with-tls/README.md @@ -8,14 +8,14 @@ - [1. Generate self-signed certificates](#1-generate-self-signed-certificates) - [2. Run samples with Docker](#2-run-samples-with-docker) - [3. Run samples locally (without Docker)](#3-run-samples-locally-without-docker) - - [3.1 Install certificate - Linux (Ubuntu, Debian)](#31-install-certificate---linux-ubuntu-debian) + - [3.1 Install certificate - Linux (Ubuntu, Debian, WSL) or MacOS](#31-install-certificate---linux-ubuntu-debian) - [3.2 Install certificate - Windows](#32-install-certificate---windows) - - [3.3. Run EventStoreDB node](#33-run-eventstoredb-node) + - [3.3 Run EventStoreDB node](#33-run-eventstoredb-node) - [3.3 Run client application](#33-run-client-application) ## Overview -The sample shows how to run the .NET gRPC client secured by TLS certificates. +The sample shows how to run the .NET client secured by TLS certificates. Read more in the docs: - [Security](https://developers.eventstore.com/server/v20/server/security/) @@ -23,7 +23,9 @@ Read more in the docs: - [Event Store Certificate Generation CLI](https://github.com/EventStore/es-gencert-cli) It is essential for production use to configure EventStoreDB security features to prevent unauthorised access to your data. -EventStoreDB supports gRPC with TLS and SSL. Each protocol has its security configuration, but you can only use one set of certificates for TLS and HTTPS. +EventStoreDB supports gRPC with TLS and SSL. + +Each protocol has its security configuration, but you can only use one set of certificates for TLS and HTTPS. ### Certificates @@ -44,45 +46,29 @@ While generating the certificate, you need to remember to pass: - DNS names to `-dns-names`: e.g. `localhost,eventstoredb` that will match the URLs that you will be accessing EventStoreDB nodes. -[Certificate Generation CLI](https://github.com/EventStore/es-gencert-cli) is also available as the Docker image. You can define [docker-compose configuration](./docker-compose.generate-certs.yml) as e.g.: - -```yaml -version: "3.5" - -services: - cert-gen: - image: eventstore/es-gencert-cli:1.0.2 - entrypoint: bash - command: > - -c "es-gencert-cli create-ca -out /tmp/certs/ca && - es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out \ - /tmp/certs/node -ip-addresses 127.0.0.1 -dns-names localhost,eventstoredb" - user: "1000:1000" - volumes: - - "./certs:/tmp/certs" -``` - -And run `docker-compose up` to generate certificates. +The [Certificate Generation CLI](https://github.com/EventStore/es-gencert-cli) is also available as the Docker image. Check the [docker-compose.certs.yml](./docker-compose.certs.yml) See instruction how to install certificates [below](#3-run-run-samples-locally-without-docker). You can find helpers scripts that are also installing created CA on local machine: -- Linux (Debian based) - [create-certs.sh](./create-certs.sh), +- Linux (Debian based, MacOS and WSL) - [create-certs.sh](./create-certs.sh), - Windows - [create-certs.ps1](./create-certs.ps1) ## Description -The sample shows how to connect with the gRPC client and append new event. You can run it locally or through docker configuration. +The sample shows how to connect with the client and append new event. You can run it locally or through docker configuration. Suggested order of reading: -- whole code is located in [Program.cs](./Program.cs) file -- [Dockerfile](./Dockerfile) for building sample image -- [Docker compose config](./docker-compose.yml) that has configuration for both sample client app and EventStoreDB node. +- The full code is located in [Program.cs](./Program.cs) file +- [Dockerfile](./Dockerfile) - for building the sample image +- [docker-compose.yml](./docker-compose.yml) - for running a single EventStoreDB node. +- [docker-compose.app.yml](./docker-compose.app.yml) - for running the sample client app. +- [docker-compose.certs.yml](./docker-compose.certs.yml) - for generating certificates. ## Running Sample ### 1. Generate self-signed certificates -Use following command to generate certificates: +Use following command to generate and install certificates: - Linux/MacOS ```console ./create-certs.sh @@ -91,43 +77,30 @@ Use following command to generate certificates: ```powershell .\create-certs.ps1 ``` + _Note: to regenerate certificates you need to remove the [./certs](./certs) folder._ ### 2. Run samples with Docker -Use the following command to run samples with Docker: -```consoler -docker-compose up +The following command will run both server and client with preconfigured TLS connection setup. + +```console +docker-compose -f docker-compose.yml -f docker-compose.app.yml up ``` -It will run both server and client with preconfigured TLS connection setup. ### 3. Run samples locally (without Docker) -To run samples locally, you need to install the generated CA certificate. - -#### 3.1 Install certificate - Linux (Ubuntu, Debian) -- Copy [./certs/ca/ca.crt](./certs/ca/ca.crt) to dir `/usr/local/share/ca-certificates/` -- Use command: - ```console - sudo cp foo.crt /usr/local/share/ca-certificates/foo.crt - ``` -- Update the CA store: - ```console - sudo update-ca-certificates - ``` -#### 3.2 Install certificate - Windows -Windows certificate will be automatically installed when [./create-certs.ps1](./create-certs.ps1) was run. +Assuming the certificates were generated and installed. -#### 3.3. Run EventStoreDB node +#### 3.1 Run EventStoreDB Use the following command to run EventStoreDB ```console -docker-compose up eventstoredb +docker-compose up -d ``` -#### 3.3 Run client application -Run application from console: +#### 3.2 Run client application +Run the application from your favourite IDE or the console: ```console dotnet run ./secure-with-tls.csproj ``` -or from your favourite IDE. diff --git a/samples/secure-with-tls/create-certs.ps1 b/samples/secure-with-tls/create-certs.ps1 index b53185cda..34fabb178 100644 --- a/samples/secure-with-tls/create-certs.ps1 +++ b/samples/secure-with-tls/create-certs.ps1 @@ -1,5 +1,5 @@ New-Item -ItemType Directory -Force -Path certs -docker-compose -f docker-compose.generate-certs.yml up +docker-compose -f docker-compose.certs.yml up --remove-orphans Import-Certificate -FilePath ".\certs\ca\ca.crt" -CertStoreLocation Cert:\CurrentUser\Root diff --git a/samples/secure-with-tls/create-certs.sh b/samples/secure-with-tls/create-certs.sh old mode 100644 new mode 100755 index 2fc0777b2..e51e2a296 --- a/samples/secure-with-tls/create-certs.sh +++ b/samples/secure-with-tls/create-certs.sh @@ -1,2 +1,31 @@ +unameOutput="$(uname -sr)" +case "${unameOutput}" in + Linux*Microsoft*) machine=WSL;; + Linux*) machine=Linux;; + Darwin*) machine=MacOS;; + *) machine="${unameOutput}" +esac + +echo ">> Generating certificate..." mkdir -p certs -docker-compose -f docker-compose.generate-certs.yml up +docker-compose -f docker-compose.certs.yml up --remove-orphans + +echo ">> Copying certificate..." +cp certs/ca/ca.crt /usr/local/share/ca-certificates/eventstore_ca.crt +#rsync --progress certs/ca/ca.crt /usr/local/share/ca-certificates/eventstore_ca.crt + +echo ">> Updating certificate permissions..." +#chmod 644 /usr/local/share/ca-certificates/eventstore_ca.crt + +if [ "${machine}" == "MacOS" ]; then + echo ">> Installing certificate on ${machine}..." + sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /usr/local/share/ca-certificates/eventstore_ca.crt +elif [ "$(machine)" == "Linux" ]; then + echo ">> Installing certificate on ${machine}..." + sudo update-ca-certificates +elif [ "$(machine)" == "WSL" ]; then + echo ">> Installing certificate on ${machine}..." + sudo update-ca-certificates +else + echo ">> Unknown platform. Please install the certificate manually." +fi \ No newline at end of file diff --git a/samples/secure-with-tls/docker-compose.app.yml b/samples/secure-with-tls/docker-compose.app.yml new file mode 100644 index 000000000..b96f84ae8 --- /dev/null +++ b/samples/secure-with-tls/docker-compose.app.yml @@ -0,0 +1,19 @@ +version: "3.5" + +networks: + default: + name: eventstore-network + +services: + + app: + container_name: app + build: ./ + environment: + # URL should match the DNS name in certificate and container name + - ESDB__CONNECTION__STRING=esdb://admin:changeit@eventstore:2113?Tls=true&tlsVerifyCert=false + depends_on: + eventstore: + condition: service_healthy + links: + - eventstore diff --git a/samples/secure-with-tls/docker-compose.certs.yml b/samples/secure-with-tls/docker-compose.certs.yml new file mode 100644 index 000000000..56c6278dc --- /dev/null +++ b/samples/secure-with-tls/docker-compose.certs.yml @@ -0,0 +1,30 @@ +version: "3.5" + +networks: + default: + name: eventstore-network + +services: + + volumes-provisioner: + image: hasnat/volumes-provisioner + container_name: volumes-provisioner + environment: + PROVISION_DIRECTORIES: "1000:1000:0755:/tmp/certs" + volumes: + - "./certs:/tmp/certs" + network_mode: none + + cert-gen: + image: eventstore/es-gencert-cli:1.0.2 + container_name: cert-gen + user: "1000:1000" + entrypoint: [ "/bin/sh","-c" ] + command: + - | + es-gencert-cli create-ca -out /tmp/certs/ca + es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out /tmp/certs/node -ip-addresses 127.0.0.1 -dns-names localhost,eventstore + volumes: + - "./certs:/tmp/certs" + depends_on: + - volumes-provisioner diff --git a/samples/secure-with-tls/docker-compose.generate-certs.yml b/samples/secure-with-tls/docker-compose.generate-certs.yml deleted file mode 100644 index ec62ab85a..000000000 --- a/samples/secure-with-tls/docker-compose.generate-certs.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: "3.5" - -services: - cert-gen: - image: eventstore/es-gencert-cli:1.0.2 - entrypoint: bash - # dns-name in this case `eventstoredb` matches EventStoreDB container_name and URL it's accessed at `docker-compose.yml` - command: > - -c "es-gencert-cli create-ca -out /tmp/certs/ca && - es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out \ - /tmp/certs/node -ip-addresses 127.0.0.1 -dns-names localhost,eventstoredb" - user: "1000:1000" - volumes: - - "./certs:/tmp/certs" diff --git a/samples/secure-with-tls/docker-compose.yml b/samples/secure-with-tls/docker-compose.yml index 53ad888e6..f1078440f 100644 --- a/samples/secure-with-tls/docker-compose.yml +++ b/samples/secure-with-tls/docker-compose.yml @@ -1,43 +1,38 @@ version: "3.5" -services: - grpc.client: - build: ./ - environment: - # URL should match the DNS name in certificate and container name - - ESDB_CONNECTION_STRING=esdb://eventstoredb:2113?Tls=true - networks: - - esdb_network - depends_on: - eventstoredb: - condition: service_healthy +networks: + default: + name: eventstore-network - eventstoredb: - image: eventstore/eventstore:20.10.0-buster-slim - # container_name should match the DNS name in certificate - container_name: eventstoredb - environment: - - EVENTSTORE_CLUSTER_SIZE=1 - - EVENTSTORE_RUN_PROJECTIONS=All - - EVENTSTORE_ENABLE_EXTERNAL_TCP=true - - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true - - EVENTSTORE_EXT_TCP_PORT=1113 - - EVENTSTORE_EXT_HTTP_PORT=2113 - # set certificates location - - EVENTSTORE_CERTIFICATE_FILE=/etc/eventstore/certs/node/node.crt - - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/etc/eventstore/certs/node/node.key - - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/etc/eventstore/certs/ca - ports: - - 1113:1113 - - 2113:2113 - networks: - - esdb_network - volumes: - # define volume that will copy pregenerated certificates - - ./certs:/etc/eventstore/certs - restart: unless-stopped +services: + + eventstore: + image: eventstore/eventstore:latest + container_name: eventstore + environment: + - EVENTSTORE_MEM_DB=true + - EVENTSTORE_HTTP_PORT=2113 + - EVENTSTORE_LOG_LEVEL=Information + - EVENTSTORE_RUN_PROJECTIONS=None + - EVENTSTORE_START_STANDARD_PROJECTIONS=true + - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true + + # set certificates location + - EVENTSTORE_CERTIFICATE_FILE=/etc/eventstore/certs/node/node.crt + - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/etc/eventstore/certs/node/node.key + - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/etc/eventstore/certs/ca + ports: + - "2113:2113" + volumes: + - ./certs:/etc/eventstore/certs + - type: volume + source: eventstore-volume-data1 + target: /var/lib/eventstore + - type: volume + source: eventstore-volume-logs1 + target: /var/log/eventstore + restart: unless-stopped -networks: - esdb_network: - name: eventstoredb.local - driver: bridge +volumes: + eventstore-volume-data1: + eventstore-volume-logs1: diff --git a/samples/secure-with-tls/run-app.sh b/samples/secure-with-tls/run-app.sh new file mode 100755 index 000000000..4bf5f1e27 --- /dev/null +++ b/samples/secure-with-tls/run-app.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +echo "Going down..." +docker-compose -f docker-compose.yml -f docker-compose.app.yml down + +echo "Going up..." +docker-compose -f docker-compose.yml -f docker-compose.app.yml up --remove-orphans diff --git a/samples/secure-with-tls/secure-with-tls.csproj b/samples/secure-with-tls/secure-with-tls.csproj index 149de2f9b..7dd4b7c03 100644 --- a/samples/secure-with-tls/secure-with-tls.csproj +++ b/samples/secure-with-tls/secure-with-tls.csproj @@ -1,11 +1,21 @@ + Exe + net8.0 secure_with_tls + enable + enable + Linux + + + + + - + - \ No newline at end of file + diff --git a/samples/server-side-filtering/Program.cs b/samples/server-side-filtering/Program.cs index e2e2c814e..243e40b60 100644 --- a/samples/server-side-filtering/Program.cs +++ b/samples/server-side-filtering/Program.cs @@ -1,174 +1,176 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client; +#pragma warning disable CS8321 // Local function is declared but never used + using EventTypeFilter = EventStore.Client.EventTypeFilter; -namespace server_side_filtering { - class Program { - static async Task Main() { - const int eventCount = 100; - var semaphore = new SemaphoreSlim(eventCount); - - await using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://localhost:2113?tls=false") - ); - - await client.SubscribeToAllAsync(FromAll.Start, - (s, e, c) => { - Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); - semaphore.Release(); - return Task.CompletedTask; - }, - filterOptions: new SubscriptionFilterOptions( - EventTypeFilter.Prefix("some-"), - 1, - (s, p, c) => { - Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); - return Task.CompletedTask; - }) - ); - - await Task.Delay(2000); - - for (var i = 0; i < eventCount; i++) { - var eventData = new EventData( - Uuid.NewUuid(), - i % 2 == 0 ? "some-event" : "other-event", - Encoding.UTF8.GetBytes("{\"id\": \"1\" \"value\": \"some value\"}") - ); - - await client.AppendToStreamAsync( - Guid.NewGuid().ToString("N"), - StreamRevision.None, - new List {eventData} - ); - } - - await semaphore.WaitAsync(); +const int eventCount = 100; + +var semaphore = new SemaphoreSlim(eventCount); + +await using var client = new EventStoreClient(EventStoreClientSettings.Create("esdb://localhost:2113?tls=false")); + +await client.SubscribeToAllAsync( + FromAll.Start, + (s, e, c) => { + Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); + semaphore.Release(); + return Task.CompletedTask; + }, + filterOptions: new SubscriptionFilterOptions( + EventTypeFilter.Prefix("some-"), + 1, + (s, p, c) => { + Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); + return Task.CompletedTask; } + ) +); + +await Task.Delay(2000); + +for (var i = 0; i < eventCount; i++) { + var eventData = new EventData( + Uuid.NewUuid(), + i % 2 == 0 ? "some-event" : "other-event", + "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() + ); + + await client.AppendToStreamAsync( + Guid.NewGuid().ToString("N"), + StreamRevision.None, + new List { eventData } + ); +} - private static async Task ExcludeSystemEvents(EventStoreClient client) { - #region exclude-system - await client.SubscribeToAllAsync(FromAll.Start, - (s, e, c) => { - Console.WriteLine( - $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); - return Task.CompletedTask; - }, - filterOptions: new SubscriptionFilterOptions( - EventTypeFilter.ExcludeSystemEvents()) - ); - #endregion exclude-system - } +await semaphore.WaitAsync(); - private static async Task EventTypePrefix(EventStoreClient client) { - #region event-type-prefix - var filter = new SubscriptionFilterOptions( - EventTypeFilter.Prefix("customer-")); - #endregion event-type-prefix - - await client.SubscribeToAllAsync(FromAll.Start, - (s, e, c) => { - Console.WriteLine( - $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); - return Task.CompletedTask; - }, - filterOptions: filter - ); - } +return; - private static async Task EventTypeRegex(EventStoreClient client) { - #region event-type-regex - var filter = new SubscriptionFilterOptions( - EventTypeFilter.RegularExpression("^user|^company")); - #endregion event-type-regex - - await client.SubscribeToAllAsync(FromAll.Start, - (s, e, c) => { - Console.WriteLine( - $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); - return Task.CompletedTask; - }, - filterOptions: filter - ); - } +static async Task ExcludeSystemEvents(EventStoreClient client) { + #region exclude-system - private static async Task StreamPrefix(EventStoreClient client) { - #region stream-prefix - var filter = new SubscriptionFilterOptions( - StreamFilter.Prefix("user-")); - #endregion stream-prefix - - await client.SubscribeToAllAsync(FromAll.Start, - (s, e, c) => { - Console.WriteLine( - $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); - return Task.CompletedTask; - }, - filterOptions: filter - ); - } + await client.SubscribeToAllAsync( + FromAll.Start, + (s, e, c) => { + Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); + return Task.CompletedTask; + }, + filterOptions: new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents()) + ); - private static async Task StreamRegex(EventStoreClient client) { - #region stream-regex - var filter = new SubscriptionFilterOptions( - StreamFilter.RegularExpression("^account|^savings")); - #endregion stream-regex - - await client.SubscribeToAllAsync(FromAll.Start, - (s, e, c) => { - Console.WriteLine( - $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); - return Task.CompletedTask; - }, - filterOptions: filter - ); - } + #endregion exclude-system +} - private static async Task CheckpointCallback(EventStoreClient client) { - #region checkpoint - var filter = new SubscriptionFilterOptions( - EventTypeFilter.ExcludeSystemEvents(), - checkpointReached: (s, p, c) => - { - Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); - return Task.CompletedTask; - }); - #endregion checkpoint - - await client.SubscribeToAllAsync(FromAll.Start, - (s, e, c) => { - Console.WriteLine( - $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); - return Task.CompletedTask; - }, - filterOptions: filter - ); - } +static async Task EventTypePrefix(EventStoreClient client) { + #region event-type-prefix + + var filter = new SubscriptionFilterOptions(EventTypeFilter.Prefix("customer-")); + + #endregion event-type-prefix + + await client.SubscribeToAllAsync( + FromAll.Start, + (s, e, c) => { + Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); + return Task.CompletedTask; + }, + filterOptions: filter + ); +} + +static async Task EventTypeRegex(EventStoreClient client) { + #region event-type-regex + + var filter = new SubscriptionFilterOptions(EventTypeFilter.RegularExpression("^user|^company")); + + #endregion event-type-regex - private static async Task CheckpointCallbackWithInterval(EventStoreClient client) { - #region checkpoint-with-interval - var filter = new SubscriptionFilterOptions( - EventTypeFilter.ExcludeSystemEvents(), - checkpointInterval: 1000, - checkpointReached: (s, p, c) => - { - Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); - return Task.CompletedTask; - }); - #endregion checkpoint-with-interval - - await client.SubscribeToAllAsync(FromAll.Start, - (s, e, c) => { - Console.WriteLine( - $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); - return Task.CompletedTask; - }, - filterOptions: filter - ); + await client.SubscribeToAllAsync( + FromAll.Start, + (s, e, c) => { + Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); + return Task.CompletedTask; + }, + filterOptions: filter + ); +} + +static async Task StreamPrefix(EventStoreClient client) { + #region stream-prefix + + var filter = new SubscriptionFilterOptions(StreamFilter.Prefix("user-")); + + #endregion stream-prefix + + await client.SubscribeToAllAsync( + FromAll.Start, + (s, e, c) => { + Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); + return Task.CompletedTask; + }, + filterOptions: filter + ); +} + +static async Task StreamRegex(EventStoreClient client) { + #region stream-regex + + var filter = new SubscriptionFilterOptions(StreamFilter.RegularExpression("^account|^savings")); + + #endregion stream-regex + + await client.SubscribeToAllAsync( + FromAll.Start, + (s, e, c) => { + Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); + return Task.CompletedTask; + }, + filterOptions: filter + ); +} + +static async Task CheckpointCallback(EventStoreClient client) { + #region checkpoint + + var filter = new SubscriptionFilterOptions( + EventTypeFilter.ExcludeSystemEvents(), + checkpointReached: (s, p, c) => { + Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); + return Task.CompletedTask; } - } + ); + + #endregion checkpoint + + await client.SubscribeToAllAsync( + FromAll.Start, + (s, e, c) => { + Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); + return Task.CompletedTask; + }, + filterOptions: filter + ); } + +static async Task CheckpointCallbackWithInterval(EventStoreClient client) { + #region checkpoint-with-interval + + var filter = new SubscriptionFilterOptions( + EventTypeFilter.ExcludeSystemEvents(), + 1000, + (s, p, c) => { + Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); + return Task.CompletedTask; + } + ); + + #endregion checkpoint-with-interval + + await client.SubscribeToAllAsync( + FromAll.Start, + (s, e, c) => { + Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); + return Task.CompletedTask; + }, + filterOptions: filter + ); +} \ No newline at end of file diff --git a/samples/server-side-filtering/server-side-filtering.csproj b/samples/server-side-filtering/server-side-filtering.csproj index 34b2d4c39..b69b90670 100644 --- a/samples/server-side-filtering/server-side-filtering.csproj +++ b/samples/server-side-filtering/server-side-filtering.csproj @@ -1,14 +1,12 @@ - server_side_filtering - + - \ No newline at end of file diff --git a/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs b/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs index d1ad74a8c..d4c3f4d59 100644 --- a/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs +++ b/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs @@ -1,12 +1,9 @@ -using System.Collections.Generic; -using EventStore.Client; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; namespace setting_up_dependency_injection.Controllers { [ApiController] [Route("[controller]")] public class EventStoreController : ControllerBase { - #region using-dependency private readonly EventStoreClient _eventStoreClient; diff --git a/samples/setting-up-dependency-injection/Program.cs b/samples/setting-up-dependency-injection/Program.cs index f1d6624d7..49b316496 100644 --- a/samples/setting-up-dependency-injection/Program.cs +++ b/samples/setting-up-dependency-injection/Program.cs @@ -1,20 +1,12 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +namespace setting_up_dependency_injection; -namespace setting_up_dependency_injection { - public class Program { - public static async Task Main(string[] args) { - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)); - await CreateHostBuilder(args).Build().WaitForShutdownAsync(cts.Token); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => { - webBuilder.UseStartup(); - }); +public class Program { + public static async Task Main(string[] args) { + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1)); + await CreateHostBuilder(args).Build().WaitForShutdownAsync(cts.Token); } -} + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); +} \ No newline at end of file diff --git a/samples/setting-up-dependency-injection/Startup.cs b/samples/setting-up-dependency-injection/Startup.cs index 7e28c1d53..a1d618aff 100644 --- a/samples/setting-up-dependency-injection/Startup.cs +++ b/samples/setting-up-dependency-injection/Startup.cs @@ -1,38 +1,25 @@ -using System; -using System.Net.Http; -using EventStore.Client; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace setting_up_dependency_injection { - public class Startup { - public Startup(IConfiguration configuration) { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void ConfigureServices(IServiceCollection services) { - services.AddControllers(); - - #region setting-up-dependency - services.AddEventStoreClient("esdb://admin:changeit@localhost:2113?tls=false"); - #endregion setting-up-dependency - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - if (env.IsDevelopment()) { - app.UseDeveloperExceptionPage(); - } - - app.UseHttpsRedirection(); - app.UseRouting(); - app.UseEndpoints(endpoints => { - endpoints.MapControllers(); - }); - } +namespace setting_up_dependency_injection; + +public class Startup { + public Startup(IConfiguration configuration) => Configuration = configuration; + + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) { + services.AddControllers(); + + #region setting-up-dependency + + services.AddEventStoreClient("esdb://admin:changeit@localhost:2113?tls=false"); + + #endregion setting-up-dependency + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); + + app.UseHttpsRedirection(); + app.UseRouting(); + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } -} +} \ No newline at end of file diff --git a/samples/setting-up-dependency-injection/setting-up-dependency-injection.csproj b/samples/setting-up-dependency-injection/setting-up-dependency-injection.csproj index 7f3b511bd..0e2adfd05 100644 --- a/samples/setting-up-dependency-injection/setting-up-dependency-injection.csproj +++ b/samples/setting-up-dependency-injection/setting-up-dependency-injection.csproj @@ -5,7 +5,7 @@ - + diff --git a/samples/subscribing-to-streams/Program.cs b/samples/subscribing-to-streams/Program.cs index 724b1b2e5..b0a59ea22 100644 --- a/samples/subscribing-to-streams/Program.cs +++ b/samples/subscribing-to-streams/Program.cs @@ -1,162 +1,185 @@ -using System; -using System.Net.Http; -using System.Reflection.Metadata; -using System.Threading; -using System.Threading.Tasks; -using EventStore.Client; -using EventStore.Client.Streams; - -namespace subscribing_to_streams { - class Program { - static async Task Main(string[] args) { - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://localhost:2113?tls=false") - ); - - await SubscribeToStream(client); - await SubscribeToAll(client); - await OverridingUserCredentials(client); - } +#pragma warning disable CS8321 // Local function is declared but never used - private static async Task SubscribeToStream(EventStoreClient client) { - #region subscribe-to-stream - await client.SubscribeToStreamAsync("some-stream", - FromStream.Start, - async (subscription, evnt, cancellationToken) => { - Console.WriteLine($"Received event {evnt.OriginalEventNumber}@{evnt.OriginalStreamId}"); - await HandleEvent(evnt); - }); - #endregion subscribe-to-stream - - #region subscribe-to-stream-from-position - await client.SubscribeToStreamAsync( - "some-stream", - FromStream.After(StreamPosition.FromInt64(20)), - EventAppeared); - #endregion subscribe-to-stream-from-position - - #region subscribe-to-stream-live - await client.SubscribeToStreamAsync( - "some-stream", - FromStream.End, - EventAppeared); - #endregion subscribe-to-stream-live - - #region subscribe-to-stream-resolving-linktos - await client.SubscribeToStreamAsync( - "$et-myEventType", - FromStream.Start, - EventAppeared, - resolveLinkTos: true); - #endregion subscribe-to-stream-resolving-linktos - - #region subscribe-to-stream-subscription-dropped - - var checkpoint = await ReadStreamCheckpointAsync(); - await client.SubscribeToStreamAsync( - "some-stream", - checkpoint is null ? FromStream.Start : FromStream.After(checkpoint.Value), - eventAppeared: async (subscription, evnt, cancellationToken) => { - await HandleEvent(evnt); - checkpoint = evnt.OriginalEventNumber; - }, - subscriptionDropped: ((subscription, reason, exception) => { - Console.WriteLine($"Subscription was dropped due to {reason}. {exception}"); - if (reason != SubscriptionDroppedReason.Disposed) { - // Resubscribe if the client didn't stop the subscription - Resubscribe(checkpoint); - } - })); - #endregion subscribe-to-stream-subscription-dropped - } +// ReSharper disable UnusedParameter.Local +// ReSharper disable UnusedVariable - private static async Task SubscribeToAll(EventStoreClient client) { - #region subscribe-to-all - await client.SubscribeToAllAsync( - FromAll.Start, - async (subscription, evnt, cancellationToken) => { - Console.WriteLine($"Received event {evnt.OriginalEventNumber}@{evnt.OriginalStreamId}"); - await HandleEvent(evnt); - }); - #endregion subscribe-to-all - - #region subscribe-to-all-from-position - - var result = await client.AppendToStreamAsync("subscribe-to-all-from-position", StreamState.NoStream, new[] { - new EventData(Uuid.NewUuid(), "-", ReadOnlyMemory.Empty) - }); - - await client.SubscribeToAllAsync( - FromAll.After(result.LogPosition), - EventAppeared); - #endregion subscribe-to-all-from-position - - #region subscribe-to-all-live - await client.SubscribeToAllAsync( - FromAll.End, - EventAppeared); - #endregion subscribe-to-all-live - - #region subscribe-to-all-subscription-dropped - var checkpoint = await ReadCheckpointAsync(); - await client.SubscribeToAllAsync( - checkpoint is null ? FromAll.Start : FromAll.After(checkpoint.Value), - eventAppeared: async (subscription, evnt, cancellationToken) => { - await HandleEvent(evnt); - checkpoint = evnt.OriginalPosition!.Value; - }, - subscriptionDropped: ((subscription, reason, exception) => { - Console.WriteLine($"Subscription was dropped due to {reason}. {exception}"); - if (reason != SubscriptionDroppedReason.Disposed) { - // Resubscribe if the client didn't stop the subscription - Resubscribe(checkpoint); - } - })); - #endregion subscribe-to-all-subscription-dropped - } +await using var client = new EventStoreClient(EventStoreClientSettings.Create("esdb://localhost:2113?tls=false")); + +await SubscribeToStream(client); +await SubscribeToAll(client); +await OverridingUserCredentials(client); + +return; - private static async Task SubscribeToFiltered(EventStoreClient client) { - #region stream-prefix-filtered-subscription - var prefixStreamFilter = new SubscriptionFilterOptions(StreamFilter.Prefix("test-", "other-")); - await client.SubscribeToAllAsync( - FromAll.Start, - EventAppeared, - filterOptions: prefixStreamFilter); - #endregion stream-prefix-filtered-subscription - - #region stream-regex-filtered-subscription - var regexStreamFilter = StreamFilter.RegularExpression(@"/invoice-\d\d\d/g"); - #endregion stream-regex-filtered-subscription +static async Task SubscribeToStream(EventStoreClient client) { + #region subscribe-to-stream + + await client.SubscribeToStreamAsync( + "some-stream", + FromStream.Start, + async (subscription, evnt, cancellationToken) => { + Console.WriteLine($"Received event {evnt.OriginalEventNumber}@{evnt.OriginalStreamId}"); + await HandleEvent(evnt); } + ); + + #endregion subscribe-to-stream + + #region subscribe-to-stream-from-position + + await client.SubscribeToStreamAsync( + "some-stream", + FromStream.After(StreamPosition.FromInt64(20)), + EventAppeared + ); + + #endregion subscribe-to-stream-from-position - private static async Task OverridingUserCredentials(EventStoreClient client) { - #region overriding-user-credentials - await client.SubscribeToAllAsync( - FromAll.Start, - EventAppeared, - userCredentials: new UserCredentials("admin", "changeit")); - #endregion overriding-user-credentials + #region subscribe-to-stream-live + + await client.SubscribeToStreamAsync( + "some-stream", + FromStream.End, + EventAppeared + ); + + #endregion subscribe-to-stream-live + + #region subscribe-to-stream-resolving-linktos + + await client.SubscribeToStreamAsync( + "$et-myEventType", + FromStream.Start, + EventAppeared, + true + ); + + #endregion subscribe-to-stream-resolving-linktos + + #region subscribe-to-stream-subscription-dropped + + var checkpoint = await ReadStreamCheckpointAsync(); + await client.SubscribeToStreamAsync( + "some-stream", + checkpoint is null ? FromStream.Start : FromStream.After(checkpoint.Value), + async (subscription, evnt, cancellationToken) => { + await HandleEvent(evnt); + checkpoint = evnt.OriginalEventNumber; + }, + subscriptionDropped: (subscription, reason, exception) => { + Console.WriteLine($"Subscription was dropped due to {reason}. {exception}"); + if (reason != SubscriptionDroppedReason.Disposed) + // Resubscribe if the client didn't stop the subscription + ResubscribeToStream(checkpoint); } + ); + + #endregion subscribe-to-stream-subscription-dropped +} - private static Task EventAppeared(StreamSubscription subscription, ResolvedEvent evnt, - CancellationToken cancellationToken) { - return Task.CompletedTask; +static async Task SubscribeToAll(EventStoreClient client) { + #region subscribe-to-all + + await client.SubscribeToAllAsync( + FromAll.Start, + async (subscription, evnt, cancellationToken) => { + Console.WriteLine($"Received event {evnt.OriginalEventNumber}@{evnt.OriginalStreamId}"); + await HandleEvent(evnt); } + ); + + #endregion subscribe-to-all + + #region subscribe-to-all-from-position - private static void SubscriptionDropped(StreamSubscription subscription, SubscriptionDroppedReason reason, - Exception ex) { + var result = await client.AppendToStreamAsync( + "subscribe-to-all-from-position", + StreamState.NoStream, + new[] { + new EventData(Uuid.NewUuid(), "-", ReadOnlyMemory.Empty) } - private static Task HandleEvent(ResolvedEvent evnt) { - return Task.CompletedTask; + ); + + await client.SubscribeToAllAsync( + FromAll.After(result.LogPosition), + EventAppeared + ); + + #endregion subscribe-to-all-from-position + + #region subscribe-to-all-live + + await client.SubscribeToAllAsync( + FromAll.End, + EventAppeared + ); + + #endregion subscribe-to-all-live + + #region subscribe-to-all-subscription-dropped + + var checkpoint = await ReadCheckpointAsync(); + await client.SubscribeToAllAsync( + checkpoint is null ? FromAll.Start : FromAll.After(checkpoint.Value), + async (subscription, evnt, cancellationToken) => { + await HandleEvent(evnt); + checkpoint = evnt.OriginalPosition!.Value; + }, + subscriptionDropped: (subscription, reason, exception) => { + Console.WriteLine($"Subscription was dropped due to {reason}. {exception}"); + if (reason != SubscriptionDroppedReason.Disposed) + // Resubscribe if the client didn't stop the subscription + Resubscribe(checkpoint); } + ); + + #endregion subscribe-to-all-subscription-dropped +} - private static void Resubscribe(StreamPosition? checkpoint) { } - private static void Resubscribe(Position? checkpoint) { } +static async Task SubscribeToFiltered(EventStoreClient client) { + #region stream-prefix-filtered-subscription - private static Task ReadStreamCheckpointAsync() => - Task.FromResult(new StreamPosition?()); + var prefixStreamFilter = new SubscriptionFilterOptions(StreamFilter.Prefix("test-", "other-")); + await client.SubscribeToAllAsync( + FromAll.Start, + EventAppeared, + filterOptions: prefixStreamFilter + ); - private static Task ReadCheckpointAsync() => - Task.FromResult(new Position?()); - } + #endregion stream-prefix-filtered-subscription + + #region stream-regex-filtered-subscription + + var regexStreamFilter = StreamFilter.RegularExpression(@"/invoice-\d\d\d/g"); + + #endregion stream-regex-filtered-subscription } + +static async Task OverridingUserCredentials(EventStoreClient client) { + #region overriding-user-credentials + + await client.SubscribeToAllAsync( + FromAll.Start, + EventAppeared, + userCredentials: new UserCredentials("admin", "changeit") + ); + + #endregion overriding-user-credentials +} + +static Task EventAppeared(StreamSubscription subscription, ResolvedEvent evnt, CancellationToken cancellationToken) => + Task.CompletedTask; + +static void SubscriptionDropped(StreamSubscription subscription, SubscriptionDroppedReason reason, Exception ex) { } + +static Task HandleEvent(ResolvedEvent evnt) => Task.CompletedTask; + +static void ResubscribeToStream(StreamPosition? checkpoint) { } + +static void Resubscribe(Position? checkpoint) { } + +static Task ReadStreamCheckpointAsync() => + Task.FromResult(new StreamPosition?()); + +static Task ReadCheckpointAsync() => + Task.FromResult(new Position?()); \ No newline at end of file diff --git a/samples/subscribing-to-streams/subscribing-to-streams.csproj b/samples/subscribing-to-streams/subscribing-to-streams.csproj index d67c0a569..397f07197 100644 --- a/samples/subscribing-to-streams/subscribing-to-streams.csproj +++ b/samples/subscribing-to-streams/subscribing-to-streams.csproj @@ -5,7 +5,7 @@ - + diff --git a/test/EventStore.Client.Tests.Common/Extensions/EnumerableTaskExtensions.cs b/src/EventStore.Client.Common/EnumerableTaskExtensions.cs similarity index 75% rename from test/EventStore.Client.Tests.Common/Extensions/EnumerableTaskExtensions.cs rename to src/EventStore.Client.Common/EnumerableTaskExtensions.cs index fd62d7b4c..eb4517006 100644 --- a/test/EventStore.Client.Tests.Common/Extensions/EnumerableTaskExtensions.cs +++ b/src/EventStore.Client.Common/EnumerableTaskExtensions.cs @@ -1,8 +1,8 @@ using System.Diagnostics; -namespace EventStore.Client.Tests; +namespace EventStore.Client; -public static class EnumerableTaskExtensions { +static class EnumerableTaskExtensions { [DebuggerStepThrough] public static Task WhenAll(this IEnumerable source) => Task.WhenAll(source); diff --git a/src/EventStore.Client.Operations/EventStoreOperationsClient.cs b/src/EventStore.Client.Operations/EventStoreOperationsClient.cs index 156d0eda0..672383348 100644 --- a/src/EventStore.Client.Operations/EventStoreOperationsClient.cs +++ b/src/EventStore.Client.Operations/EventStoreOperationsClient.cs @@ -1,38 +1,33 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Grpc.Core; +using Grpc.Core; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -namespace EventStore.Client { - /// - /// The client used to perform maintenance and other administrative tasks on the EventStoreDB. - /// - public sealed partial class EventStoreOperationsClient : EventStoreClientBase { - private static readonly IDictionary> ExceptionMap = - new Dictionary> { - [Constants.Exceptions.ScavengeNotFound] = ex => new ScavengeNotFoundException(ex.Trailers - .FirstOrDefault(x => x.Key == Constants.Exceptions.ScavengeId)?.Value) - }; +namespace EventStore.Client; - private readonly ILogger _log; +/// +/// The client used to perform maintenance and other administrative tasks on the EventStoreDB. +/// +public sealed partial class EventStoreOperationsClient : EventStoreClientBase { + static readonly Dictionary> ExceptionMap = + new() { + [Constants.Exceptions.ScavengeNotFound] = ex => new ScavengeNotFoundException( + ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.ScavengeId)?.Value + ) + }; - /// - /// Constructs a new . This method is not intended to be called directly in your code. - /// - /// - public EventStoreOperationsClient(IOptions options) : this(options.Value) { - } + readonly ILogger _log; - /// - /// Constructs a new . - /// - /// - public EventStoreOperationsClient(EventStoreClientSettings? settings = null) : base(settings, ExceptionMap) { - _log = Settings.LoggerFactory?.CreateLogger() ?? - new NullLogger(); - } - } -} + /// + /// Constructs a new . This method is not intended to be called directly in your code. + /// + /// + public EventStoreOperationsClient(IOptions options) : this(options.Value) { } + + /// + /// Constructs a new . + /// + /// + public EventStoreOperationsClient(EventStoreClientSettings? settings = null) : base(settings, ExceptionMap) => + _log = Settings.LoggerFactory?.CreateLogger() ?? new NullLogger(); +} \ No newline at end of file diff --git a/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs b/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs index 87969c677..0047e1c73 100644 --- a/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs +++ b/src/EventStore.Client.PersistentSubscriptions/PersistentSubscription.cs @@ -141,8 +141,7 @@ private async Task Subscribe() { _log.LogDebug("Persistent Subscription {subscriptionId} confirmed.", SubscriptionId); try { - await foreach (var response in - _call.ResponseStream.ReadAllAsync(_cancellationToken).ConfigureAwait(false)) { + await foreach (var response in _call.ResponseStream.ReadAllAsync(_cancellationToken).ConfigureAwait(false)) { if (response.ContentCase != ReadResp.ContentOneofCase.Event) { continue; } diff --git a/src/EventStore.Client.Streams/EventStoreClient.cs b/src/EventStore.Client.Streams/EventStoreClient.cs index cace19051..33041d303 100644 --- a/src/EventStore.Client.Streams/EventStoreClient.cs +++ b/src/EventStore.Client.Streams/EventStoreClient.cs @@ -30,35 +30,19 @@ public sealed partial class EventStoreClient : EventStoreClientBase { private static readonly Dictionary> ExceptionMap = new() { - [Constants.Exceptions.InvalidTransaction] = - ex => new InvalidTransactionException(ex.Message, ex), - [Constants.Exceptions.StreamDeleted] = ex => new StreamDeletedException( - ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.StreamName)?.Value ?? - "", - ex), - [Constants.Exceptions.WrongExpectedVersion] = ex => new WrongExpectedVersionException( - ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.StreamName)?.Value!, - ex.Trailers.GetStreamRevision(Constants.Exceptions.ExpectedVersion), - ex.Trailers.GetStreamRevision(Constants.Exceptions.ActualVersion), - ex, ex.Message), - [Constants.Exceptions.MaximumAppendSizeExceeded] = ex => - new MaximumAppendSizeExceededException( - ex.Trailers.GetIntValueOrDefault(Constants.Exceptions.MaximumAppendSize), ex), - [Constants.Exceptions.StreamNotFound] = ex => new StreamNotFoundException( - ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.StreamName)?.Value!, ex), - [Constants.Exceptions.MissingRequiredMetadataProperty] = ex => new - RequiredMetadataPropertyMissingException( - ex.Trailers.FirstOrDefault(x => - x.Key == Constants.Exceptions.MissingRequiredMetadataProperty) - ?.Value!, ex), + [Constants.Exceptions.InvalidTransaction] = ex => new InvalidTransactionException(ex.Message, ex), + [Constants.Exceptions.StreamDeleted] = ex => new StreamDeletedException(ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.StreamName)?.Value ?? "", ex), + [Constants.Exceptions.WrongExpectedVersion] = ex => new WrongExpectedVersionException(ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.StreamName)?.Value!, ex.Trailers.GetStreamRevision(Constants.Exceptions.ExpectedVersion), ex.Trailers.GetStreamRevision(Constants.Exceptions.ActualVersion), ex, ex.Message), + [Constants.Exceptions.MaximumAppendSizeExceeded] = ex => new MaximumAppendSizeExceededException(ex.Trailers.GetIntValueOrDefault(Constants.Exceptions.MaximumAppendSize), ex), + [Constants.Exceptions.StreamNotFound] = ex => new StreamNotFoundException(ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.StreamName)?.Value!, ex), + [Constants.Exceptions.MissingRequiredMetadataProperty] = ex => new RequiredMetadataPropertyMissingException(ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.MissingRequiredMetadataProperty)?.Value!, ex), }; /// /// Constructs a new . This is not intended to be called directly from your code. /// /// - public EventStoreClient(IOptions options) : this(options.Value) { - } + public EventStoreClient(IOptions options) : this(options.Value) { } /// /// Constructs a new . @@ -71,9 +55,7 @@ public EventStoreClient(EventStoreClientSettings? settings = null) : base(settin } private void SwapStreamAppender(Exception ex) => - Interlocked.Exchange(ref _streamAppenderLazy, - new Lazy(CreateStreamAppender)) - .Value.Dispose(); + Interlocked.Exchange(ref _streamAppenderLazy, new Lazy(CreateStreamAppender)).Value.Dispose(); // todo: might be nice to have two different kinds of appenders and we decide which to instantiate according to the server caps. private StreamAppender CreateStreamAppender() { diff --git a/src/EventStore.Client.UserManagement/EventStoreUserManagementClient.cs b/src/EventStore.Client.UserManagement/EventStoreUserManagementClient.cs index a0680cbf6..6b86e81b4 100644 --- a/src/EventStore.Client.UserManagement/EventStoreUserManagementClient.cs +++ b/src/EventStore.Client.UserManagement/EventStoreUserManagementClient.cs @@ -270,7 +270,7 @@ public async Task ResetPasswordAsync(string loginName, string newPassword, await call.ResponseAsync.ConfigureAwait(false); } - private static readonly IDictionary> ExceptionMap = + private static readonly Dictionary> ExceptionMap = new Dictionary> { [Constants.Exceptions.UserNotFound] = ex => new UserNotFoundException( ex.Trailers.First(x => x.Key == Constants.Exceptions.LoginName).Value), diff --git a/src/EventStore.Client/ChannelBaseExtensions.cs b/src/EventStore.Client/ChannelBaseExtensions.cs index eb078ae2e..3edbf59fd 100644 --- a/src/EventStore.Client/ChannelBaseExtensions.cs +++ b/src/EventStore.Client/ChannelBaseExtensions.cs @@ -1,15 +1,10 @@ -using System; -using System.Threading.Tasks; using Grpc.Core; -namespace EventStore.Client { - internal static class ChannelBaseExtensions { - public static async ValueTask DisposeAsync(this ChannelBase channel) { - // for grpc.core, shutdown does the cleanup and the cast returns null - // for grpc.net shutdown does nothing and dispose does the cleanup - await channel.ShutdownAsync().ConfigureAwait(false); +namespace EventStore.Client; - (channel as IDisposable)?.Dispose(); - } +static class ChannelBaseExtensions { + public static async ValueTask DisposeAsync(this ChannelBase channel) { + await channel.ShutdownAsync().ConfigureAwait(false); + (channel as IDisposable)?.Dispose(); } -} +} \ No newline at end of file diff --git a/src/EventStore.Client/ChannelCache.cs b/src/EventStore.Client/ChannelCache.cs index 1ed172bc6..a3369e25f 100644 --- a/src/EventStore.Client/ChannelCache.cs +++ b/src/EventStore.Client/ChannelCache.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - +using System.Net; using TChannel = Grpc.Net.Client.GrpcChannel; namespace EventStore.Client { @@ -11,7 +6,6 @@ namespace EventStore.Client { // Deals with the disposal difference between grpc.net and grpc.core // Thread safe. internal class ChannelCache : - IDisposable, // for grpc.net we can dispose synchronously, but not for grpc.core IAsyncDisposable { private readonly EventStoreClientSettings _settings; diff --git a/src/EventStore.Client/EventStore.Client.csproj b/src/EventStore.Client/EventStore.Client.csproj index ca10005c9..92a14142f 100644 --- a/src/EventStore.Client/EventStore.Client.csproj +++ b/src/EventStore.Client/EventStore.Client.csproj @@ -6,10 +6,10 @@ EventStore.Client.Grpc - - - - + + + + diff --git a/src/EventStore.Client/EventStoreClientBase.cs b/src/EventStore.Client/EventStoreClientBase.cs index d3252a330..39f579fcc 100644 --- a/src/EventStore.Client/EventStoreClientBase.cs +++ b/src/EventStore.Client/EventStoreClientBase.cs @@ -14,7 +14,7 @@ public abstract class EventStoreClientBase : IDisposable, // for grpc.net we can dispose synchronously, but not for grpc.core IAsyncDisposable { - private readonly IDictionary> _exceptionMap; + private readonly Dictionary> _exceptionMap; private readonly CancellationTokenSource _cts; private readonly ChannelCache _channelCache; private readonly SharingProvider _channelInfoProvider; @@ -28,7 +28,7 @@ public abstract class EventStoreClientBase : /// Constructs a new . protected EventStoreClientBase(EventStoreClientSettings? settings, - IDictionary> exceptionMap) { + Dictionary> exceptionMap) { Settings = settings ?? new EventStoreClientSettings(); _exceptionMap = exceptionMap; _cts = new CancellationTokenSource(); diff --git a/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs b/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs index f49307287..75754f95f 100644 --- a/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs +++ b/src/EventStore.Client/Interceptors/TypedExceptionInterceptor.cs @@ -1,119 +1,142 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; + +using System.Diagnostics.CodeAnalysis; using Grpc.Core; using Grpc.Core.Interceptors; +using static EventStore.Client.Constants; +using static Grpc.Core.StatusCode; + +namespace EventStore.Client.Interceptors; + +class TypedExceptionInterceptor : Interceptor { + static readonly Dictionary> DefaultExceptionMap = new() { + [Exceptions.AccessDenied] = ex => ex.ToAccessDeniedException(), + [Exceptions.NotLeader] = ex => ex.ToNotLeaderException(), + }; -namespace EventStore.Client.Interceptors { - internal class TypedExceptionInterceptor : Interceptor { - private static readonly IDictionary> DefaultExceptionMap = - new Dictionary> { - [Constants.Exceptions.AccessDenied] = ex => new AccessDeniedException(ex.Message, ex), - [Constants.Exceptions.NotLeader] = ex => new NotLeaderException( - ex.Trailers.FirstOrDefault(x => x.Key == Constants.Exceptions.LeaderEndpointHost)?.Value!, - ex.Trailers.GetIntValueOrDefault(Constants.Exceptions.LeaderEndpointPort), ex) + public TypedExceptionInterceptor(Dictionary> customExceptionMap) { + var map = new Dictionary>(DefaultExceptionMap.Concat(customExceptionMap)); + + ConvertRpcException = rpcEx => { + if (rpcEx.TryMapException(map, out var ex)) + throw ex; + + throw rpcEx.StatusCode switch { + Unavailable when rpcEx.Status.Detail == "Deadline Exceeded" => rpcEx.ToDeadlineExceededRpcException(), + Unauthenticated => rpcEx.ToNotAuthenticatedException(), + _ => rpcEx }; + }; + } - private readonly IDictionary> _exceptionMap; + Func ConvertRpcException { get; } + + public override AsyncServerStreamingCall AsyncServerStreamingCall( + TRequest request, + ClientInterceptorContext context, + AsyncServerStreamingCallContinuation continuation + ) { + var response = continuation(request, context); + + return new AsyncServerStreamingCall( + response.ResponseStream.Apply(ConvertRpcException), + response.ResponseHeadersAsync, + response.GetStatus, + response.GetTrailers, + response.Dispose + ); + } - public TypedExceptionInterceptor(IDictionary> exceptionMap) { - _exceptionMap = new Dictionary>(DefaultExceptionMap); - foreach (var pair in exceptionMap) { - _exceptionMap.Add(pair); - } - } + public override AsyncClientStreamingCall AsyncClientStreamingCall( + ClientInterceptorContext context, + AsyncClientStreamingCallContinuation continuation + ) { + var response = continuation(context); + + return new AsyncClientStreamingCall( + response.RequestStream, + response.ResponseAsync.Apply(ConvertRpcException), + response.ResponseHeadersAsync, + response.GetStatus, + response.GetTrailers, + response.Dispose + ); + } - public override AsyncServerStreamingCall AsyncServerStreamingCall( - TRequest request, - ClientInterceptorContext context, - AsyncServerStreamingCallContinuation continuation) { - var response = continuation(request, context); + public override AsyncUnaryCall AsyncUnaryCall( + TRequest request, + ClientInterceptorContext context, + AsyncUnaryCallContinuation continuation + ) { + var response = continuation(request, context); + + return new AsyncUnaryCall( + response.ResponseAsync.Apply(ConvertRpcException), + response.ResponseHeadersAsync, + response.GetStatus, + response.GetTrailers, + response.Dispose + ); + } - return new AsyncServerStreamingCall( - new AsyncStreamReader(_exceptionMap, response.ResponseStream), - response.ResponseHeadersAsync, response.GetStatus, response.GetTrailers, response.Dispose); - } + public override AsyncDuplexStreamingCall AsyncDuplexStreamingCall( + ClientInterceptorContext context, + AsyncDuplexStreamingCallContinuation continuation + ) { + var response = continuation(context); + + return new AsyncDuplexStreamingCall( + response.RequestStream, + response.ResponseStream.Apply(ConvertRpcException), + response.ResponseHeadersAsync, + response.GetStatus, + response.GetTrailers, + response.Dispose + ); + } +} - public override AsyncClientStreamingCall AsyncClientStreamingCall( - ClientInterceptorContext context, - AsyncClientStreamingCallContinuation continuation) { - var response = continuation(context); - - return new AsyncClientStreamingCall( - response.RequestStream, - response.ResponseAsync.ContinueWith(t => t.Exception?.InnerException is RpcException ex - ? throw ConvertRpcException(ex, _exceptionMap) - : t.Result), - response.ResponseHeadersAsync, - response.GetStatus, - response.GetTrailers, - response.Dispose); - } +static class RpcExceptionConversionExtensions { + public static IAsyncStreamReader Apply(this IAsyncStreamReader reader, Func convertException) => + new ExceptionConverterStreamReader(reader, convertException); - public override AsyncUnaryCall AsyncUnaryCall( - TRequest request, - ClientInterceptorContext context, - AsyncUnaryCallContinuation continuation) { - var response = continuation(request, context); - - return new AsyncUnaryCall(response.ResponseAsync.ContinueWith(t => - t.Exception?.InnerException is RpcException ex - ? throw ConvertRpcException(ex, _exceptionMap) - : t.Result), response.ResponseHeadersAsync, response.GetStatus, response.GetTrailers, - response.Dispose); - } + public static Task Apply(this Task task, Func convertException) => + task.ContinueWith(t => t.Exception?.InnerException is RpcException ex ? throw convertException(ex) : t.Result); + + public static AccessDeniedException ToAccessDeniedException(this RpcException exception) => + new(exception.Message, exception); - public override AsyncDuplexStreamingCall AsyncDuplexStreamingCall( - ClientInterceptorContext context, - AsyncDuplexStreamingCallContinuation continuation) { - var response = continuation(context); - - return new AsyncDuplexStreamingCall( - response.RequestStream, - new AsyncStreamReader(_exceptionMap, response.ResponseStream), - response.ResponseHeadersAsync, - response.GetStatus, - response.GetTrailers, - response.Dispose); - } + public static NotLeaderException ToNotLeaderException(this RpcException exception) { + var host = exception.Trailers.FirstOrDefault(x => x.Key == Exceptions.LeaderEndpointHost)?.Value!; + var port = exception.Trailers.GetIntValueOrDefault(Exceptions.LeaderEndpointPort); + return new NotLeaderException(host, port, exception); + } + + public static NotAuthenticatedException ToNotAuthenticatedException(this RpcException exception) => + new(exception.Message, exception); - private static Exception ConvertRpcException(RpcException ex, - IDictionary> exceptionMap) { - Func? factory = null; - return (ex.Trailers.TryGetValue(Constants.Exceptions.ExceptionKey, out var key) && - exceptionMap.TryGetValue(key!, out factory)) switch { - true => factory!.Invoke(ex), - false => (ex.StatusCode, ex.Status.Detail) switch { - (StatusCode.Unavailable, "Deadline Exceeded") => new RpcException(new Status( - StatusCode.DeadlineExceeded, ex.Status.Detail, ex.Status.DebugException)), - (StatusCode.DeadlineExceeded, _) => ex, - (StatusCode.Unauthenticated, _) => new NotAuthenticatedException(ex.Message, ex), - _ => ex - } - }; + public static RpcException ToDeadlineExceededRpcException(this RpcException exception) => + new(new Status(DeadlineExceeded, exception.Status.Detail, exception.Status.DebugException)); + + public static bool TryMapException(this RpcException exception, Dictionary> map, [MaybeNullWhen(false)] out Exception createdException) { + if (exception.Trailers.TryGetValue(Exceptions.ExceptionKey, out var key) && map.TryGetValue(key!, out var factory)) { + createdException = factory.Invoke(exception); + return true; } - private class AsyncStreamReader : IAsyncStreamReader { - private readonly IDictionary> _exceptionMap; - private readonly IAsyncStreamReader _inner; - - public AsyncStreamReader(IDictionary> exceptionMap, - IAsyncStreamReader inner) { - _exceptionMap = exceptionMap; - _inner = inner; - } - - public async Task MoveNext(CancellationToken cancellationToken) { - try { - return await _inner.MoveNext(cancellationToken).ConfigureAwait(false); - } catch (RpcException ex) { - throw ConvertRpcException(ex, _exceptionMap); - } - } - - public TResponse Current => _inner.Current; + createdException = null; + return false; + } +} + +class ExceptionConverterStreamReader(IAsyncStreamReader reader, Func convertException) : IAsyncStreamReader { + public TResponse Current => reader.Current; + + public async Task MoveNext(CancellationToken cancellationToken) { + try { + return await reader.MoveNext(cancellationToken).ConfigureAwait(false); + } + catch (RpcException ex) { + throw convertException(ex); } } } diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 213f077ff..a4de0b3a7 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -9,12 +9,12 @@ - + - + - - + + all runtime; build; native; contentfiles; analyzers @@ -25,6 +25,7 @@ + diff --git a/test/EventStore.Client.Streams.Tests/Append/ShouldThrowAsyncExtensions.cs b/test/EventStore.Client.Streams.Tests/Append/ShouldThrowAsyncExtensions.cs new file mode 100644 index 000000000..80f983ce0 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Append/ShouldThrowAsyncExtensions.cs @@ -0,0 +1,14 @@ +namespace EventStore.Client.Streams.Tests.Append; + +public static class ShouldThrowAsyncExtensions { + public static Task ShouldThrowAsync(this EventStoreClient.ReadStreamResult source) where TException : Exception => + source + .ToArrayAsync() + .AsTask() + .ShouldThrowAsync(); + + public static async Task ShouldThrowAsync(this EventStoreClient.ReadStreamResult source, Action handler) where TException : Exception { + var ex = await source.ShouldThrowAsync(); + handler(ex); + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Append/append_to_stream.cs b/test/EventStore.Client.Streams.Tests/Append/append_to_stream.cs new file mode 100644 index 000000000..b4ad7d49e --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Append/append_to_stream.cs @@ -0,0 +1,475 @@ +using Grpc.Core; + +namespace EventStore.Client.Streams.Tests.Append; + +[Trait("Category", "Target:Stream")] +[Trait("Category", "Operation:Append")] +public class append_to_stream(ITestOutputHelper output, EventStoreFixture fixture) : EventStoreTests(output, fixture) { + public static IEnumerable ExpectedVersionCreateStreamTestCases() { + yield return new object?[] { StreamState.Any }; + yield return new object?[] { StreamState.NoStream }; + } + + [Theory] + [MemberData(nameof(ExpectedVersionCreateStreamTestCases))] + public async Task appending_zero_events(StreamState expectedStreamState) { + var stream = $"{Fixture.GetStreamName()}_{expectedStreamState}"; + + const int iterations = 2; + for (var i = 0; i < iterations; i++) { + var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, expectedStreamState, Enumerable.Empty()); + writeResult.NextExpectedStreamRevision.ShouldBe(StreamRevision.None); + } + + await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, iterations) + .ShouldThrowAsync(ex => ex.Stream.ShouldBe(stream)); + } + + [Theory] + [MemberData(nameof(ExpectedVersionCreateStreamTestCases))] + public async Task appending_zero_events_again(StreamState expectedStreamState) { + var stream = $"{Fixture.GetStreamName()}_{expectedStreamState}"; + + const int iterations = 2; + for (var i = 0; i < iterations; i++) { + var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, expectedStreamState, Enumerable.Empty()); + Assert.Equal(StreamRevision.None, writeResult.NextExpectedStreamRevision); + } + + await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, iterations) + .ShouldThrowAsync(ex => ex.Stream.ShouldBe(stream)); + } + + [Theory] + [MemberData(nameof(ExpectedVersionCreateStreamTestCases))] + public async Task create_stream_expected_version_on_first_write_if_does_not_exist(StreamState expectedStreamState) { + var stream = $"{Fixture.GetStreamName()}_{expectedStreamState}"; + + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + expectedStreamState, + Fixture.CreateTestEvents(1) + ); + + Assert.Equal(new(0), writeResult.NextExpectedStreamRevision); + + var count = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 2) + .CountAsync(); + + Assert.Equal(1, count); + } + + [Fact] + public async Task multiple_idempotent_writes() { + var stream = Fixture.GetStreamName(); + var events = Fixture.CreateTestEvents(4).ToArray(); + + var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events); + Assert.Equal(new(3), writeResult.NextExpectedStreamRevision); + + writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events); + Assert.Equal(new(3), writeResult.NextExpectedStreamRevision); + } + + [Fact] + public async Task multiple_idempotent_writes_with_same_id_bug_case() { + var stream = Fixture.GetStreamName(); + + var evnt = Fixture.CreateTestEvents().First(); + var events = new[] { evnt, evnt, evnt, evnt, evnt, evnt }; + + var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events); + + Assert.Equal(new(5), writeResult.NextExpectedStreamRevision); + } + + [Fact] + public async Task in_case_where_multiple_writes_of_multiple_events_with_the_same_ids_using_expected_version_any_then_next_expected_version_is_unreliable() { + var stream = Fixture.GetStreamName(); + + var evnt = Fixture.CreateTestEvents().First(); + var events = new[] { evnt, evnt, evnt, evnt, evnt, evnt }; + + var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events); + + Assert.Equal(new(5), writeResult.NextExpectedStreamRevision); + + writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events); + + Assert.Equal(new(0), writeResult.NextExpectedStreamRevision); + } + + [Fact] + public async Task in_case_where_multiple_writes_of_multiple_events_with_the_same_ids_using_expected_version_nostream_then_next_expected_version_is_correct() { + var stream = Fixture.GetStreamName(); + + var evnt = Fixture.CreateTestEvents().First(); + var events = new[] { evnt, evnt, evnt, evnt, evnt, evnt }; + var streamRevision = StreamRevision.FromInt64(events.Length - 1); + + var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + Assert.Equal(streamRevision, writeResult.NextExpectedStreamRevision); + + writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + Assert.Equal(streamRevision, writeResult.NextExpectedStreamRevision); + } + + [Fact] + public async Task writing_with_correct_expected_version_to_deleted_stream_throws_stream_deleted() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream); + + await Fixture.Streams + .AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents(1)) + .ShouldThrowAsync(); + } + + [Fact] + public async Task returns_log_position_when_writing() { + var stream = Fixture.GetStreamName(); + + var result = await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.NoStream, + Fixture.CreateTestEvents(1) + ); + + Assert.True(0 < result.LogPosition.PreparePosition); + Assert.True(0 < result.LogPosition.CommitPosition); + } + + [Fact] + public async Task writing_with_any_expected_version_to_deleted_stream_throws_stream_deleted() { + var stream = Fixture.GetStreamName(); + await Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream); + + await Fixture.Streams + .AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents(1)) + .ShouldThrowAsync(); + } + + [Fact] + public async Task writing_with_invalid_expected_version_to_deleted_stream_throws_stream_deleted() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream); + + await Fixture.Streams + .AppendToStreamAsync(stream, new StreamRevision(5), Fixture.CreateTestEvents()) + .ShouldThrowAsync(); + } + + [Fact] + public async Task append_with_correct_expected_version_to_existing_stream() { + var stream = Fixture.GetStreamName(); + + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.NoStream, + Fixture.CreateTestEvents(1) + ); + + writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + writeResult.NextExpectedStreamRevision, + Fixture.CreateTestEvents() + ); + + Assert.Equal(new(1), writeResult.NextExpectedStreamRevision); + } + + [Fact] + public async Task append_with_any_expected_version_to_existing_stream() { + var stream = Fixture.GetStreamName(); + + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.NoStream, + Fixture.CreateTestEvents(1) + ); + + Assert.Equal(new(0), writeResult.NextExpectedStreamRevision); + + writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.Any, + Fixture.CreateTestEvents(1) + ); + + Assert.Equal(new(1), writeResult.NextExpectedStreamRevision); + } + + [Fact] + public async Task appending_with_wrong_expected_version_to_existing_stream_throws_wrong_expected_version() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents()); + + var ex = await Fixture.Streams + .AppendToStreamAsync(stream, new StreamRevision(999), Fixture.CreateTestEvents()) + .ShouldThrowAsync(); + + ex.ActualStreamRevision.ShouldBe(new(0)); + ex.ExpectedStreamRevision.ShouldBe(new(999)); + } + + [Fact] + public async Task appending_with_wrong_expected_version_to_existing_stream_returns_wrong_expected_version() { + var stream = Fixture.GetStreamName(); + + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + new StreamRevision(1), + Fixture.CreateTestEvents(), + options => { options.ThrowOnAppendFailure = false; } + ); + + var wrongExpectedVersionResult = (WrongExpectedVersionResult)writeResult; + + Assert.Equal(new(1), wrongExpectedVersionResult.NextExpectedStreamRevision); + } + + [Fact] + public async Task append_with_stream_exists_expected_version_to_existing_stream() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents()); + + await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.StreamExists, + Fixture.CreateTestEvents() + ); + } + + [Fact] + public async Task append_with_stream_exists_expected_version_to_stream_with_multiple_events() { + var stream = Fixture.GetStreamName(); + + for (var i = 0; i < 5; i++) + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents(1)); + + await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.StreamExists, + Fixture.CreateTestEvents() + ); + } + + [Fact] + public async Task append_with_stream_exists_expected_version_if_metadata_stream_exists() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.SetStreamMetadataAsync( + stream, + StreamState.Any, + new(10, default) + ); + + await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.StreamExists, + Fixture.CreateTestEvents() + ); + } + + [Fact] + public async Task appending_with_stream_exists_expected_version_and_stream_does_not_exist_throws_wrong_expected_version() { + var stream = Fixture.GetStreamName(); + + var ex = await Fixture.Streams + .AppendToStreamAsync(stream, StreamState.StreamExists, Fixture.CreateTestEvents()) + .ShouldThrowAsync(); + + ex.ActualStreamRevision.ShouldBe(StreamRevision.None); + } + + [Fact] + public async Task appending_with_stream_exists_expected_version_and_stream_does_not_exist_returns_wrong_expected_version() { + var stream = Fixture.GetStreamName(); + + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.StreamExists, + Fixture.CreateTestEvents(), + options => { options.ThrowOnAppendFailure = false; } + ); + + var wrongExpectedVersionResult = Assert.IsType(writeResult); + + Assert.Equal(StreamRevision.None, wrongExpectedVersionResult.NextExpectedStreamRevision); + } + + [Fact] + public async Task appending_with_stream_exists_expected_version_to_hard_deleted_stream_throws_stream_deleted() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream); + + await Fixture.Streams + .AppendToStreamAsync(stream, StreamState.StreamExists, Fixture.CreateTestEvents()) + .ShouldThrowAsync(); + } + + [Fact] + public async Task appending_with_stream_exists_expected_version_to_deleted_stream_throws_stream_deleted() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents()); + + await Fixture.Streams.DeleteAsync(stream, StreamState.Any); + + await Fixture.Streams + .AppendToStreamAsync(stream, StreamState.StreamExists, Fixture.CreateTestEvents()) + .ShouldThrowAsync(); + } + + [Fact] + public async Task can_append_multiple_events_at_once() { + var stream = Fixture.GetStreamName(); + + var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents(100)); + + Assert.Equal(new(99), writeResult.NextExpectedStreamRevision); + } + + [Fact] + public async Task returns_failure_status_when_conditionally_appending_with_version_mismatch() { + var stream = Fixture.GetStreamName(); + + var result = await Fixture.Streams.ConditionalAppendToStreamAsync( + stream, + new StreamRevision(7), + Fixture.CreateTestEvents() + ); + + Assert.Equal( + ConditionalWriteResult.FromWrongExpectedVersion(new(stream, new StreamRevision(7), StreamRevision.None)), + result + ); + } + + [Fact] + public async Task returns_success_status_when_conditionally_appending_with_matching_version() { + var stream = Fixture.GetStreamName(); + + var result = await Fixture.Streams.ConditionalAppendToStreamAsync( + stream, + StreamState.Any, + Fixture.CreateTestEvents() + ); + + Assert.Equal( + ConditionalWriteResult.FromWriteResult(new SuccessResult(0, result.LogPosition)), + result + ); + } + + [Fact] + public async Task returns_failure_status_when_conditionally_appending_to_a_deleted_stream() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents()); + + await Fixture.Streams.TombstoneAsync(stream, StreamState.Any); + + var result = await Fixture.Streams.ConditionalAppendToStreamAsync( + stream, + StreamState.Any, + Fixture.CreateTestEvents() + ); + + Assert.Equal(ConditionalWriteResult.StreamDeleted, result); + } + + [Fact] + public async Task expected_version_no_stream() { + var result = await Fixture.Streams.AppendToStreamAsync( + Fixture.GetStreamName(), + StreamState.NoStream, + Fixture.CreateTestEvents() + ); + + Assert.Equal(new(0), result!.NextExpectedStreamRevision); + } + + [Fact] + public async Task expected_version_no_stream_returns_position() { + var result = await Fixture.Streams.AppendToStreamAsync( + Fixture.GetStreamName(), + StreamState.NoStream, + Fixture.CreateTestEvents() + ); + + Assert.True(result.LogPosition > Position.Start); + } + + [Fact] + public async Task with_timeout_any_stream_revision_fails_when_operation_expired() { + var stream = Fixture.GetStreamName(); + + var ex = await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.Any, + Fixture.CreateTestEvents(100), + deadline: TimeSpan.FromTicks(1) + ).ShouldThrowAsync(); + + ex.StatusCode.ShouldBe(StatusCode.DeadlineExceeded); + } + + [Fact] + public async Task with_timeout_stream_revision_fails_when_operation_expired() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents()); + + var ex = await Fixture.Streams.AppendToStreamAsync( + stream, + new StreamRevision(0), + Fixture.CreateTestEvents(10), + deadline: TimeSpan.Zero + ).ShouldThrowAsync(); + + ex.StatusCode.ShouldBe(StatusCode.DeadlineExceeded); + } + + [Fact] + public async Task when_events_enumerator_throws_the_write_does_not_succeed() { + var streamName = Fixture.GetStreamName(); + + await Fixture.Streams + .AppendToStreamAsync(streamName, StreamRevision.None, GetEvents()) + .ShouldThrowAsync(); + + var result = Fixture.Streams.ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start); + + var state = await result.ReadState; + + state.ShouldBe(ReadState.StreamNotFound); + + return; + + IEnumerable GetEvents() { + var i = 0; + foreach (var evt in Fixture.CreateTestEvents(5)) { + if (i++ % 3 == 0) + throw new EnumerationFailedException(); + + yield return evt; + } + } + } + + class EnumerationFailedException : Exception { } + + public static IEnumerable ArgumentOutOfRangeTestCases() { + yield return new object?[] { StreamState.Any }; + yield return new object?[] { ulong.MaxValue - 1UL }; + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Append/append_to_stream_limits.cs b/test/EventStore.Client.Streams.Tests/Append/append_to_stream_limits.cs new file mode 100644 index 000000000..6bdd3fbb8 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Append/append_to_stream_limits.cs @@ -0,0 +1,54 @@ +namespace EventStore.Client.Streams.Tests.Append; + +[Trait("Category", "Target:Stream")] +[Trait("Category", "Operation:Append")] +public class append_to_stream_limits(ITestOutputHelper output, StreamLimitsFixture fixture) : EventStoreTests(output, fixture) { + [Fact] + public async Task succeeds_when_size_is_less_than_max_append_size() { + var stream = Fixture.GetStreamName(); + + var (events, size) = Fixture.CreateTestEventsUpToMaxSize(StreamLimitsFixture.MaxAppendSize - 1); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + } + + [Fact] + public async Task fails_when_size_exceeds_max_append_size() { + var stream = Fixture.GetStreamName(); + + var eventsAppendSize = StreamLimitsFixture.MaxAppendSize * 2; + + // beware of the size of the events... + var (events, size) = Fixture.CreateTestEventsUpToMaxSize(eventsAppendSize); + + size.ShouldBeGreaterThan(StreamLimitsFixture.MaxAppendSize); + + var ex = await Fixture.Streams + .AppendToStreamAsync(stream, StreamState.NoStream, events) + .ShouldThrowAsync(); + + ex.MaxAppendSize.ShouldBe(StreamLimitsFixture.MaxAppendSize); + } +} + +public class StreamLimitsFixture() : EventStoreFixture(x => x.WithMaxAppendSize(MaxAppendSize)) { + public const uint MaxAppendSize = 64; + + public (IEnumerable Events, uint size) CreateTestEventsUpToMaxSize(uint maxSize) { + var size = 0; + var events = new List(); + + foreach (var evt in CreateTestEvents(int.MaxValue)) { + size += evt.Data.Length; + + if (size >= maxSize) { + size -= evt.Data.Length; + break; + } + + events.Add(evt); + } + + return (events, (uint)size); + } +} diff --git a/test/EventStore.Client.Streams.Tests/Append/append_to_stream_retry.cs b/test/EventStore.Client.Streams.Tests/Append/append_to_stream_retry.cs new file mode 100644 index 000000000..d67240c49 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Append/append_to_stream_retry.cs @@ -0,0 +1,37 @@ +using Polly; +using Polly.Contrib.WaitAndRetry; + +namespace EventStore.Client.Streams.Tests.Append; + +[Trait("Category", "Target:Stream")] +[Trait("Category", "Operation:Append")] +public class append_to_stream_retry(ITestOutputHelper output, StreamRetryFixture fixture) : EventStoreTests(output, fixture) { + [Fact] + public async Task can_retry() { + var stream = Fixture.GetStreamName(); + + // can definitely write without throwing + var result = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents()); + + result.NextExpectedStreamRevision.ShouldBe(new(0)); + + await Fixture.Service.Restart(); + + // write can be retried + var writeResult = await Policy + .Handle() + .WaitAndRetryAsync( + Backoff.LinearBackoff(TimeSpan.FromMilliseconds(250), 10), + (ex, ts) => Fixture.Log.Debug("Error writing events to stream. Retrying. Reason: {Message}.", ex.Message) + ) + .ExecuteAsync(() => Fixture.Streams.AppendToStreamAsync(stream, result.NextExpectedStreamRevision, Fixture.CreateTestEvents())); + + Fixture.Log.Information("Successfully wrote events to stream {Stream}.", stream); + + writeResult.NextExpectedStreamRevision.ShouldBe(new(1)); + } +} + +public class StreamRetryFixture() : EventStoreFixture( + x => x.RunInMemory(false).With(o => o.ClientSettings.ConnectivitySettings.MaxDiscoverAttempts = 2) +); \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Append/appending_to_implicitly_created_stream.cs b/test/EventStore.Client.Streams.Tests/Append/appending_to_implicitly_created_stream.cs new file mode 100644 index 000000000..59ab89df0 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Append/appending_to_implicitly_created_stream.cs @@ -0,0 +1,266 @@ +namespace EventStore.Client.Streams.Tests.Append; + +[Trait("Category", "Target:Stream")] +[Trait("Category", "Operation:Append")] +public class appending_to_implicitly_created_stream(ITestOutputHelper output, EventStoreFixture fixture) : EventStoreTests(output, fixture) { + [Fact] + public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0em1_idempotent() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(6).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(1)); + + var count = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); + + Assert.Equal(events.Length, count); + } + + [Fact] + public async Task sequence_0em1_1e0_2e1_3e2_4e3_4e4_0any_idempotent() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(6).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Take(1)); + + var count = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); + + Assert.Equal(events.Length, count); + } + + [Fact] + public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e5_non_idempotent() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(6).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Streams.AppendToStreamAsync(stream, new StreamRevision(5), events.Take(1)); + + var count = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 2).CountAsync(); + + Assert.Equal(events.Length + 1, count); + } + + [Fact] + public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e6_throws_wev() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(6).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + await Assert.ThrowsAsync(() => Fixture.Streams.AppendToStreamAsync(stream, new StreamRevision(6), events.Take(1))); + } + + [Fact] + public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e6_returns_wev() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(6).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + new StreamRevision(6), + events.Take(1), + options => options.ThrowOnAppendFailure = false + ); + + Assert.IsType(writeResult); + } + + [Fact] + public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e4_throws_wev() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(6).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + await Assert.ThrowsAsync(() => Fixture.Streams.AppendToStreamAsync(stream, new StreamRevision(4), events.Take(1))); + } + + [Fact] + public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e4_returns_wev() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(6).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + new StreamRevision(4), + events.Take(1), + options => options.ThrowOnAppendFailure = false + ); + + Assert.IsType(writeResult); + } + + [Fact] + public async Task sequence_0em1_0e0_non_idempotent() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents().ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Streams.AppendToStreamAsync(stream, new StreamRevision(0), events.Take(1)); + + var count = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 2).CountAsync(); + + Assert.Equal(events.Length + 1, count); + } + + [Fact] + public async Task sequence_0em1_0any_idempotent() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents().ToArray(); + + await Task.Delay(TimeSpan.FromSeconds(30)); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Take(1)); + + var count = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); + + Assert.Equal(events.Length, count); + } + + [Fact] + public async Task sequence_0em1_0em1_idempotent() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents().ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(1)); + + var count = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); + + Assert.Equal(events.Length, count); + } + + [Fact] + public async Task sequence_0em1_1e0_2e1_1any_1any_idempotent() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(3).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Skip(1).Take(1)); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Skip(1).Take(1)); + + var count = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); + + Assert.Equal(events.Length, count); + } + + [Fact] + public async Task sequence_S_0em1_1em1_E_S_0em1_E_idempotent() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(2).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(1)); + + var count = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); + + Assert.Equal(events.Length, count); + } + + [Fact] + public async Task sequence_S_0em1_1em1_E_S_0any_E_idempotent() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(2).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Take(1)); + + var count = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); + + Assert.Equal(events.Length, count); + } + + [Fact] + public async Task sequence_S_0em1_1em1_E_S_1e0_E_idempotent() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(2).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + await Fixture.Streams.AppendToStreamAsync(stream, new StreamRevision(0), events.Skip(1)); + + var count = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); + + Assert.Equal(events.Length, count); + } + + [Fact] + public async Task sequence_S_0em1_1em1_E_S_1any_E_idempotent() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(2).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Skip(1).Take(1)); + + var count = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); + + Assert.Equal(events.Length, count); + } + + [Fact] + public async Task sequence_S_0em1_1em1_E_S_0em1_1em1_2em1_E_idempotancy_fail_throws() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(3).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(2)); + + await Assert.ThrowsAsync( + () => Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.NoStream, + events + ) + ); + } + + [Fact] + public async Task sequence_S_0em1_1em1_E_S_0em1_1em1_2em1_E_idempotancy_fail_returns() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(3).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(2)); + + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.NoStream, + events, + options => options.ThrowOnAppendFailure = false + ); + + Assert.IsType(writeResult); + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Append/sending_and_receiving_large_messages.cs b/test/EventStore.Client.Streams.Tests/Append/sending_and_receiving_large_messages.cs new file mode 100644 index 000000000..099ece45c --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Append/sending_and_receiving_large_messages.cs @@ -0,0 +1,29 @@ +using Grpc.Core; + +namespace EventStore.Client.Streams.Tests.Append; + +[Trait("Category", "Target:Stream")] +[Trait("Category", "Operation:Append")] +public class sending_and_receiving_large_messages(ITestOutputHelper output, sending_and_receiving_large_messages.CustomFixture fixture) + : EventStoreTests(output, fixture) { + [Fact] + public async Task over_the_hard_limit() { + var streamName = Fixture.GetStreamName(); + var largeEvent = Fixture.CreateTestEvents() + .Select(e => new EventData(e.EventId, "-", new byte[CustomFixture.MaximumSize + 1])); + + var ex = await Assert.ThrowsAsync( + () => Fixture.Streams.AppendToStreamAsync( + streamName, + StreamState.NoStream, + largeEvent + ) + ); + + Assert.Equal(StatusCode.ResourceExhausted, ex.StatusCode); + } + + public class CustomFixture() : EventStoreFixture(x => x.WithMaxAppendSize(MaximumSize)) { + public const int MaximumSize = 16 * 1024 * 1024 - 10000; + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Bugs/Issue_104.cs b/test/EventStore.Client.Streams.Tests/Bugs/Issue_104.cs index 515fdf053..67815004d 100644 --- a/test/EventStore.Client.Streams.Tests/Bugs/Issue_104.cs +++ b/test/EventStore.Client.Streams.Tests/Bugs/Issue_104.cs @@ -1,21 +1,18 @@ -namespace EventStore.Client.Streams.Tests.Bugs; - -public class Issue_104 : IClassFixture { - readonly Fixture _fixture; - - public Issue_104(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests.Bugs; +[Trait("Category", "Bug")] +public class Issue_104(ITestOutputHelper output, EventStoreFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task subscription_does_not_send_checkpoint_reached_after_disposal() { - var streamName = _fixture.GetStreamName(); + var streamName = Fixture.GetStreamName(); var ignoredStreamName = $"ignore_{streamName}"; var subscriptionDisposed = new TaskCompletionSource(); var eventAppeared = new TaskCompletionSource(); var checkpointReachAfterDisposed = new TaskCompletionSource(); - await _fixture.Client.AppendToStreamAsync(streamName, StreamRevision.None, _fixture.CreateTestEvents()); + await Fixture.Streams.AppendToStreamAsync(streamName, StreamRevision.None, Fixture.CreateTestEvents()); - var subscription = await _fixture.Client.SubscribeToAllAsync( + var subscription = await Fixture.Streams.SubscribeToAllAsync( FromAll.Start, (_, _, _) => { eventAppeared.TrySetResult(true); @@ -42,20 +39,14 @@ public async Task subscription_does_not_send_checkpoint_reached_after_disposal() subscription.Dispose(); await subscriptionDisposed.Task; - await _fixture.Client.AppendToStreamAsync( + await Fixture.Streams.AppendToStreamAsync( ignoredStreamName, StreamRevision.None, - _fixture.CreateTestEvents(50) + Fixture.CreateTestEvents(50) ); var delay = Task.Delay(300); var result = await Task.WhenAny(delay, checkpointReachAfterDisposed.Task); - Assert.Equal(delay, result); // iow 300ms have passed without seeing checkpointReachAfterDisposed - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - - protected override Task When() => Task.CompletedTask; + result.ShouldBe(delay); // iow 300ms have passed without seeing checkpointReachAfterDisposed } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Bugs/Issue_2544.cs b/test/EventStore.Client.Streams.Tests/Bugs/Issue_2544.cs index 696836a94..26f9dbd83 100644 --- a/test/EventStore.Client.Streams.Tests/Bugs/Issue_2544.cs +++ b/test/EventStore.Client.Streams.Tests/Bugs/Issue_2544.cs @@ -2,16 +2,11 @@ namespace EventStore.Client.Streams.Tests.Bugs; -public class Issue_2544 : IAsyncLifetime { - const int BatchSize = 18; - const int Batches = 4; - readonly TaskCompletionSource _completed; - readonly Fixture _fixture; - readonly Dictionary _seen; - - public Issue_2544(ITestOutputHelper outputHelper) { - _fixture = new(); - _fixture.CaptureLogs(outputHelper); +[Trait("Category", "Bug")] +public class Issue_2544 : IClassFixture { + public Issue_2544(ITestOutputHelper output, EventStoreFixture fixture) { + Fixture = fixture.With(x => x.CaptureTestRun(output)); + _seen = Enumerable.Range(0, 1 + Batches * BatchSize) .Select(i => new StreamPosition((ulong)i)) .ToDictionary(r => r, _ => false); @@ -19,22 +14,25 @@ public Issue_2544(ITestOutputHelper outputHelper) { _completed = new(); } - public Task InitializeAsync() => _fixture.InitializeAsync(); + EventStoreFixture Fixture { get; } - public Task DisposeAsync() => _fixture.DisposeAsync(); + const int BatchSize = 18; + const int Batches = 4; + + readonly TaskCompletionSource _completed; + readonly Dictionary _seen; public static IEnumerable TestCases() => - Enumerable.Range(0, 5) - .Select(i => new object[] { i }); + Enumerable.Range(0, 5).Select(i => new object[] { i }); [Theory] [MemberData(nameof(TestCases))] public async Task subscribe_to_stream(int iteration) { - var streamName = $"{_fixture.GetStreamName()}_{iteration}"; + var streamName = $"{Fixture.GetStreamName()}_{iteration}"; var startFrom = FromStream.Start; async Task Subscribe() => - await _fixture.Client + await Fixture.Streams .SubscribeToStreamAsync( streamName, startFrom, @@ -53,11 +51,11 @@ await _fixture.Client [Theory] [MemberData(nameof(TestCases))] public async Task subscribe_to_all(int iteration) { - var streamName = $"{_fixture.GetStreamName()}_{iteration}"; + var streamName = $"{Fixture.GetStreamName()}_{iteration}"; var startFrom = FromAll.Start; async Task Subscribe() => - await _fixture.Client + await Fixture.Streams .SubscribeToAllAsync( startFrom, (_, e, _) => EventAppeared(e, streamName, out startFrom), @@ -75,11 +73,11 @@ await _fixture.Client [Theory] [MemberData(nameof(TestCases))] public async Task subscribe_to_all_filtered(int iteration) { - var streamName = $"{_fixture.GetStreamName()}_{iteration}"; + var streamName = $"{Fixture.GetStreamName()}_{iteration}"; var startFrom = FromAll.Start; async Task Subscribe() => - await _fixture.Client + await Fixture.Streams .SubscribeToAllAsync( startFrom, (_, e, _) => EventAppeared(e, streamName, out startFrom), @@ -102,19 +100,19 @@ async Task AppendEvents(string streamName) { for (var i = 0; i < Batches; i++) { if (expectedRevision == StreamRevision.None) { - var result = await _fixture.Client.AppendToStreamAsync( + var result = await Fixture.Streams.AppendToStreamAsync( streamName, StreamState.NoStream, - _fixture.CreateTestEvents(BatchSize) + Fixture.CreateTestEvents(BatchSize) ); expectedRevision = result.NextExpectedStreamRevision; } else { - var result = await _fixture.Client.AppendToStreamAsync( + var result = await Fixture.Streams.AppendToStreamAsync( streamName, expectedRevision, - _fixture.CreateTestEvents(BatchSize) + Fixture.CreateTestEvents(BatchSize) ); expectedRevision = result.NextExpectedStreamRevision; @@ -123,7 +121,7 @@ async Task AppendEvents(string streamName) { await Task.Delay(TimeSpan.FromMilliseconds(10)); } - await _fixture.Client.AppendToStreamAsync( + await Fixture.Streams.AppendToStreamAsync( streamName, expectedRevision, new[] { @@ -170,22 +168,4 @@ Task EventAppeared(ResolvedEvent e, string streamName) { return Task.CompletedTask; } - - public class Fixture : EventStoreClientFixture { - public Fixture() : base( - env: new() { - ["EVENTSTORE_LOG_LEVEL"] = "Verbose" - } - ) { } - - protected override Task Given() => - Client.SetStreamMetadataAsync( - SystemStreams.AllStream, - StreamState.Any, - new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root - ); - - protected override Task When() => Task.CompletedTask; - } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Delete/deleting_stream.cs b/test/EventStore.Client.Streams.Tests/Delete/deleting_stream.cs new file mode 100644 index 000000000..552a8a4b9 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Delete/deleting_stream.cs @@ -0,0 +1,124 @@ +using Grpc.Core; + +namespace EventStore.Client.Streams.Tests.Delete; + +[Trait("Category", "Operation:Delete")] +public class deleting_stream(ITestOutputHelper output, EventStoreFixture fixture) : EventStoreTests(output, fixture) { + public static IEnumerable ExpectedStreamStateCases() { + yield return new object?[] { StreamState.Any, nameof(StreamState.Any) }; + yield return new object?[] { StreamState.NoStream, nameof(StreamState.NoStream) }; + } + + [Theory] + [MemberData(nameof(ExpectedStreamStateCases))] + public async Task hard_deleting_a_stream_that_does_not_exist_with_expected_version_does_not_throw(StreamState expectedVersion, string name) { + var stream = $"{Fixture.GetStreamName()}_{name}"; + + await Fixture.Streams.TombstoneAsync(stream, expectedVersion); + } + + [Regression.Fact(21, "fixed by")] + public async Task soft_deleting_a_stream_that_exists() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamRevision.None, Fixture.CreateTestEvents()); + + await Fixture.Streams.DeleteAsync(stream, StreamState.StreamExists); + } + + [Fact] + public async Task hard_deleting_a_stream_that_does_not_exist_with_wrong_expected_version_throws() { + var stream = Fixture.GetStreamName(); + + await Assert.ThrowsAsync(() => Fixture.Streams.TombstoneAsync(stream, new StreamRevision(0))); + } + + [Fact] + public async Task soft_deleting_a_stream_that_does_not_exist_with_wrong_expected_version_throws() { + var stream = Fixture.GetStreamName(); + + await Assert.ThrowsAsync(() => Fixture.Streams.DeleteAsync(stream, new StreamRevision(0))); + } + + [Fact] + public async Task hard_deleting_a_stream_should_return_log_position() { + var stream = Fixture.GetStreamName(); + + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.NoStream, + Fixture.CreateTestEvents() + ); + + var deleteResult = await Fixture.Streams.TombstoneAsync(stream, writeResult.NextExpectedStreamRevision); + + Assert.True(deleteResult.LogPosition > writeResult.LogPosition); + } + + [Fact] + public async Task soft_deleting_a_stream_should_return_log_position() { + var stream = Fixture.GetStreamName(); + + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.NoStream, + Fixture.CreateTestEvents() + ); + + var deleteResult = await Fixture.Streams.DeleteAsync(stream, writeResult.NextExpectedStreamRevision); + + Assert.True(deleteResult.LogPosition > writeResult.LogPosition); + } + + [Fact] + public async Task hard_deleting_a_deleted_stream_should_throw() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream); + + await Assert.ThrowsAsync(() => Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream)); + } + + + [Fact] + public async Task with_timeout_any_stream_revision_delete_fails_when_operation_expired() { + var stream = Fixture.GetStreamName(); + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.DeleteAsync(stream, StreamState.Any, TimeSpan.Zero) + ); + + Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); + } + + [Fact] + public async Task with_timeout_stream_revision_delete_fails_when_operation_expired() { + var stream = Fixture.GetStreamName(); + + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.DeleteAsync(stream, new StreamRevision(0), TimeSpan.Zero) + ); + + Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); + } + + [Fact] + public async Task with_timeout_any_stream_revision_tombstoning_fails_when_operation_expired() { + var stream = Fixture.GetStreamName(); + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.TombstoneAsync(stream, StreamState.Any, TimeSpan.Zero) + ); + + Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); + } + + [Fact] + public async Task with_timeout_stream_revision_tombstoning_fails_when_operation_expired() { + var stream = Fixture.GetStreamName(); + + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.TombstoneAsync(stream, new StreamRevision(0), TimeSpan.Zero) + ); + + Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/soft_deleted_stream.cs b/test/EventStore.Client.Streams.Tests/Delete/soft_deleted_stream.cs similarity index 64% rename from test/EventStore.Client.Streams.Tests/soft_deleted_stream.cs rename to test/EventStore.Client.Streams.Tests/Delete/soft_deleted_stream.cs index d8e82cfeb..25877d2ee 100644 --- a/test/EventStore.Client.Streams.Tests/soft_deleted_stream.cs +++ b/test/EventStore.Client.Streams.Tests/Delete/soft_deleted_stream.cs @@ -1,40 +1,37 @@ using System.Text.Json; -namespace EventStore.Client.Streams.Tests; +namespace EventStore.Client.Streams.Tests.Delete; -[Trait("Category", "LongRunning")] -public class deleted_stream : IClassFixture { - readonly JsonDocument _customMetadata; - readonly Fixture _fixture; - - public deleted_stream(Fixture fixture) { - _fixture = fixture; +[Trait("Category", "Operation:Delete")] +public class deleted_stream(ITestOutputHelper output, EventStoreFixture fixture) : EventStoreTests(output, fixture) { + static JsonDocument CustomMetadata { get; } + static deleted_stream() { var customMetadata = new Dictionary { ["key1"] = true, ["key2"] = 17, ["key3"] = "some value" }; - _customMetadata = JsonDocument.Parse(JsonSerializer.Serialize(customMetadata)); + CustomMetadata = JsonDocument.Parse(JsonSerializer.Serialize(customMetadata)); } - + [Fact] public async Task reading_throws() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); - var writeResult = await _fixture.Client.AppendToStreamAsync( + var writeResult = await Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, - _fixture.CreateTestEvents() + Fixture.CreateTestEvents() ); Assert.Equal(new(0), writeResult.NextExpectedStreamRevision); - await _fixture.Client.DeleteAsync(stream, writeResult.NextExpectedStreamRevision); + await Fixture.Streams.DeleteAsync(stream, writeResult.NextExpectedStreamRevision); await Assert.ThrowsAsync( - () => _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + () => Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) .ToArrayAsync().AsTask() ); } @@ -47,27 +44,27 @@ await Assert.ThrowsAsync( [Theory] [MemberData(nameof(RecreatingTestCases))] public async Task recreated_with_any_expected_version(StreamState expectedState, string name) { - var stream = $"{_fixture.GetStreamName()}_{name}"; + var stream = $"{Fixture.GetStreamName()}_{name}"; - var writeResult = await _fixture.Client.AppendToStreamAsync( + var writeResult = await Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, - _fixture.CreateTestEvents() + Fixture.CreateTestEvents() ); Assert.Equal(new(0), writeResult.NextExpectedStreamRevision); - await _fixture.Client.DeleteAsync(stream, writeResult.NextExpectedStreamRevision); + await Fixture.Streams.DeleteAsync(stream, writeResult.NextExpectedStreamRevision); - var events = _fixture.CreateTestEvents(3).ToArray(); + var events = Fixture.CreateTestEvents(3).ToArray(); - writeResult = await _fixture.Client.AppendToStreamAsync(stream, expectedState, events); + writeResult = await Fixture.Streams.AppendToStreamAsync(stream, expectedState, events); Assert.Equal(new(3), writeResult.NextExpectedStreamRevision); await Task.Delay(50); //TODO: This is a workaround until github issue #1744 is fixed - var actual = await _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) .Select(x => x.Event) .ToArrayAsync(); @@ -78,28 +75,28 @@ public async Task recreated_with_any_expected_version(StreamState expectedState, actual.Select(x => x.EventNumber) ); - var metadata = await _fixture.Client.GetStreamMetadataAsync(stream); + var metadata = await Fixture.Streams.GetStreamMetadataAsync(stream); Assert.Equal(new StreamPosition(1), metadata.Metadata.TruncateBefore); Assert.Equal(new StreamPosition(1), metadata.MetastreamRevision); } [Fact] public async Task recreated_with_expected_version() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); - var writeResult = await _fixture.Client.AppendToStreamAsync( + var writeResult = await Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, - _fixture.CreateTestEvents() + Fixture.CreateTestEvents() ); Assert.Equal(new(0), writeResult.NextExpectedStreamRevision); - await _fixture.Client.DeleteAsync(stream, writeResult.NextExpectedStreamRevision); + await Fixture.Streams.DeleteAsync(stream, writeResult.NextExpectedStreamRevision); - var events = _fixture.CreateTestEvents(3).ToArray(); + var events = Fixture.CreateTestEvents(3).ToArray(); - writeResult = await _fixture.Client.AppendToStreamAsync( + writeResult = await Fixture.Streams.AppendToStreamAsync( stream, writeResult.NextExpectedStreamRevision, events @@ -109,7 +106,7 @@ public async Task recreated_with_expected_version() { await Task.Delay(50); //TODO: This is a workaround until github issue #1744 is fixed - var actual = await _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) .Select(x => x.Event) .ToArrayAsync(); @@ -120,7 +117,7 @@ public async Task recreated_with_expected_version() { actual.Select(x => x.EventNumber) ); - var metadata = await _fixture.Client.GetStreamMetadataAsync(stream); + var metadata = await Fixture.Streams.GetStreamMetadataAsync(stream); Assert.Equal(new StreamPosition(1), metadata.Metadata.TruncateBefore); Assert.Equal(new StreamPosition(1), metadata.MetastreamRevision); } @@ -128,12 +125,13 @@ public async Task recreated_with_expected_version() { [Fact] public async Task recreated_preserves_metadata_except_truncate_before() { const int count = 2; - var stream = _fixture.GetStreamName(); - var writeResult = await _fixture.Client.AppendToStreamAsync( + var stream = Fixture.GetStreamName(); + + var writeResult = await Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, - _fixture.CreateTestEvents(count) + Fixture.CreateTestEvents(count) ); Assert.Equal(new(1), writeResult.NextExpectedStreamRevision); @@ -142,10 +140,10 @@ public async Task recreated_preserves_metadata_except_truncate_before() { acl: new(deleteRole: "some-role"), maxCount: 100, truncateBefore: new StreamPosition(long.MaxValue), // 1 less than End - customMetadata: _customMetadata + customMetadata: CustomMetadata ); - writeResult = await _fixture.Client.SetStreamMetadataAsync( + writeResult = await Fixture.Streams.SetStreamMetadataAsync( stream, StreamState.NoStream, streamMetadata @@ -153,13 +151,13 @@ public async Task recreated_preserves_metadata_except_truncate_before() { Assert.Equal(new(0), writeResult.NextExpectedStreamRevision); - var events = _fixture.CreateTestEvents(3).ToArray(); + var events = Fixture.CreateTestEvents(3).ToArray(); - await _fixture.Client.AppendToStreamAsync(stream, new StreamRevision(1), events); + await Fixture.Streams.AppendToStreamAsync(stream, new StreamRevision(1), events); await Task.Delay(500); //TODO: This is a workaround until github issue #1744 is fixed - var actual = await _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) .Select(x => x.Event) .ToArrayAsync(); @@ -179,30 +177,30 @@ public async Task recreated_preserves_metadata_except_truncate_before() { streamMetadata.CustomMetadata ); - var metadataResult = await _fixture.Client.GetStreamMetadataAsync(stream); + var metadataResult = await Fixture.Streams.GetStreamMetadataAsync(stream); Assert.Equal(new StreamPosition(1), metadataResult.MetastreamRevision); Assert.Equal(expected, metadataResult.Metadata); } [Fact] public async Task can_be_hard_deleted() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); var writeResult = - await _fixture.Client.AppendToStreamAsync( + await Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, - _fixture.CreateTestEvents(2) + Fixture.CreateTestEvents(2) ); Assert.Equal(new(1), writeResult.NextExpectedStreamRevision); - await _fixture.Client.DeleteAsync(stream, new StreamRevision(1)); + await Fixture.Streams.DeleteAsync(stream, new StreamRevision(1)); - await _fixture.Client.TombstoneAsync(stream, StreamState.Any); + await Fixture.Streams.TombstoneAsync(stream, StreamState.Any); var ex = await Assert.ThrowsAsync( - () => _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + () => Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) .ToArrayAsync().AsTask() ); @@ -210,69 +208,69 @@ await _fixture.Client.AppendToStreamAsync( ex = await Assert.ThrowsAsync( () - => _fixture.Client.GetStreamMetadataAsync(stream) + => Fixture.Streams.GetStreamMetadataAsync(stream) ); Assert.Equal(SystemStreams.MetastreamOf(stream), ex.Stream); - await Assert.ThrowsAsync(() => _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, _fixture.CreateTestEvents())); + await Assert.ThrowsAsync( + () => Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents()) + ); } [Fact] public async Task allows_recreating_for_first_write_only_throws_wrong_expected_version() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); - var writeResult = - await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - _fixture.CreateTestEvents(2) - ); + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.NoStream, + Fixture.CreateTestEvents(2) + ); Assert.Equal(new(1), writeResult.NextExpectedStreamRevision); - await _fixture.Client.DeleteAsync(stream, new StreamRevision(1)); + await Fixture.Streams.DeleteAsync(stream, new StreamRevision(1)); - writeResult = await _fixture.Client.AppendToStreamAsync( + writeResult = await Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, - _fixture.CreateTestEvents(3) + Fixture.CreateTestEvents(3) ); Assert.Equal(new(4), writeResult.NextExpectedStreamRevision); await Assert.ThrowsAsync( - () => _fixture.Client.AppendToStreamAsync( + () => Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, - _fixture.CreateTestEvents() + Fixture.CreateTestEvents() ) ); } [Fact] public async Task allows_recreating_for_first_write_only_returns_wrong_expected_version() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); - var writeResult = - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents(2)); + var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents(2)); Assert.Equal(new(1), writeResult.NextExpectedStreamRevision); - await _fixture.Client.DeleteAsync(stream, new StreamRevision(1)); + await Fixture.Streams.DeleteAsync(stream, new StreamRevision(1)); - writeResult = await _fixture.Client.AppendToStreamAsync( + writeResult = await Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, - _fixture.CreateTestEvents(3) + Fixture.CreateTestEvents(3) ); Assert.Equal(new(4), writeResult.NextExpectedStreamRevision); - var wrongExpectedVersionResult = await _fixture.Client.AppendToStreamAsync( + var wrongExpectedVersionResult = await Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, - _fixture.CreateTestEvents(), + Fixture.CreateTestEvents(), options => options.ThrowOnAppendFailure = false ); @@ -281,31 +279,31 @@ public async Task allows_recreating_for_first_write_only_returns_wrong_expected_ [Fact] public async Task appends_multiple_writes_expected_version_any() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); var writeResult = - await _fixture.Client.AppendToStreamAsync( + await Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, - _fixture.CreateTestEvents(2) + Fixture.CreateTestEvents(2) ); Assert.Equal(new(1), writeResult.NextExpectedStreamRevision); - await _fixture.Client.DeleteAsync(stream, new StreamRevision(1)); + await Fixture.Streams.DeleteAsync(stream, new StreamRevision(1)); - var firstEvents = _fixture.CreateTestEvents(3).ToArray(); - var secondEvents = _fixture.CreateTestEvents(2).ToArray(); + var firstEvents = Fixture.CreateTestEvents(3).ToArray(); + var secondEvents = Fixture.CreateTestEvents(2).ToArray(); - writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, firstEvents); + writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, firstEvents); Assert.Equal(new(4), writeResult.NextExpectedStreamRevision); - writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, secondEvents); + writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, secondEvents); Assert.Equal(new(6), writeResult.NextExpectedStreamRevision); - var actual = await _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) .Select(x => x.Event) .ToArrayAsync(); @@ -315,7 +313,7 @@ await _fixture.Client.AppendToStreamAsync( actual.Select(x => x.EventNumber) ); - var metadataResult = await _fixture.Client.GetStreamMetadataAsync(stream); + var metadataResult = await Fixture.Streams.GetStreamMetadataAsync(stream); Assert.Equal(new StreamPosition(2), metadataResult.Metadata.TruncateBefore); Assert.Equal(new StreamPosition(1), metadataResult.MetastreamRevision); @@ -323,16 +321,16 @@ await _fixture.Client.AppendToStreamAsync( [Fact] public async Task recreated_on_empty_when_metadata_set() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); var streamMetadata = new StreamMetadata( acl: new(deleteRole: "some-role"), maxCount: 100, truncateBefore: new StreamPosition(0), - customMetadata: _customMetadata + customMetadata: CustomMetadata ); - var writeResult = await _fixture.Client.SetStreamMetadataAsync( + var writeResult = await Fixture.Streams.SetStreamMetadataAsync( stream, StreamState.NoStream, streamMetadata @@ -347,7 +345,7 @@ public async Task recreated_on_empty_when_metadata_set() { Assert.Equal(new(0), writeResult.NextExpectedStreamRevision); await Assert.ThrowsAsync( - () => _fixture.Client + () => Fixture.Streams .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) .ToArrayAsync().AsTask() ); @@ -361,7 +359,7 @@ await Assert.ThrowsAsync( streamMetadata.CustomMetadata ); - var metadataResult = await _fixture.Client.GetStreamMetadataAsync(stream); + var metadataResult = await Fixture.Streams.GetStreamMetadataAsync(stream); Assert.Equal(new StreamPosition(0), metadataResult.MetastreamRevision); Assert.Equal(expected, metadataResult.Metadata); } @@ -369,25 +367,26 @@ await Assert.ThrowsAsync( [Fact] public async Task recreated_on_non_empty_when_metadata_set() { const int count = 2; - var stream = _fixture.GetStreamName(); + + var stream = Fixture.GetStreamName(); var streamMetadata = new StreamMetadata( acl: new(deleteRole: "some-role"), maxCount: 100, - customMetadata: _customMetadata + customMetadata: CustomMetadata ); - var writeResult = await _fixture.Client.AppendToStreamAsync( + var writeResult = await Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, - _fixture.CreateTestEvents(count) + Fixture.CreateTestEvents(count) ); Assert.Equal(new(1), writeResult.NextExpectedStreamRevision); - await _fixture.Client.DeleteAsync(stream, writeResult.NextExpectedStreamRevision); + await Fixture.Streams.DeleteAsync(stream, writeResult.NextExpectedStreamRevision); - writeResult = await _fixture.Client.SetStreamMetadataAsync( + writeResult = await Fixture.Streams.SetStreamMetadataAsync( stream, new StreamRevision(0), streamMetadata @@ -403,13 +402,13 @@ public async Task recreated_on_non_empty_when_metadata_set() { // truncated events can be read await Task.Delay(200); - var actual = await _fixture.Client + var actual = await Fixture.Streams .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) .ToArrayAsync(); Assert.Empty(actual); - var metadataResult = await _fixture.Client.GetStreamMetadataAsync(stream); + var metadataResult = await Fixture.Streams.GetStreamMetadataAsync(stream); var expected = new StreamMetadata( streamMetadata.MaxCount, streamMetadata.MaxAge, @@ -421,9 +420,4 @@ public async Task recreated_on_non_empty_when_metadata_set() { Assert.Equal(expected, metadataResult.Metadata); } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/DependencyInjectionTests.cs b/test/EventStore.Client.Streams.Tests/DependencyInjectionTests.cs index 6f5ea952f..96512557b 100644 --- a/test/EventStore.Client.Streams.Tests/DependencyInjectionTests.cs +++ b/test/EventStore.Client.Streams.Tests/DependencyInjectionTests.cs @@ -3,6 +3,7 @@ namespace EventStore.Client.Streams.Tests; +[Trait("Category", "UnitTest")] public class DependencyInjectionTests { [Fact] public void Register() => diff --git a/test/EventStore.Client.Streams.Tests/EventDataComparer.cs b/test/EventStore.Client.Streams.Tests/EventDataComparer.cs deleted file mode 100644 index 780268a76..000000000 --- a/test/EventStore.Client.Streams.Tests/EventDataComparer.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Text; - -namespace EventStore.Client.Streams.Tests; - -static class EventDataComparer { - public static bool Equal(EventData expected, EventRecord actual) { - if (expected.EventId != actual.EventId) - return false; - - if (expected.Type != actual.EventType) - return false; - - var expectedDataString = Encoding.UTF8.GetString(expected.Data.ToArray()); - var expectedMetadataString = Encoding.UTF8.GetString(expected.Metadata.ToArray()); - - var actualDataString = Encoding.UTF8.GetString(actual.Data.ToArray()); - var actualMetadataDataString = Encoding.UTF8.GetString(actual.Metadata.ToArray()); - - return expectedDataString == actualDataString && expectedMetadataString == actualMetadataDataString; - } - - public static bool Equal(EventData[] expected, EventRecord[] actual) { - if (expected.Length != actual.Length) - return false; - - for (var i = 0; i < expected.Length; i++) - if (!Equal(expected[i], actual[i])) - return false; - - return true; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/EventDataTests.cs b/test/EventStore.Client.Streams.Tests/EventDataTests.cs index b5cf352da..87ed00191 100644 --- a/test/EventStore.Client.Streams.Tests/EventDataTests.cs +++ b/test/EventStore.Client.Streams.Tests/EventDataTests.cs @@ -1,11 +1,11 @@ namespace EventStore.Client.Streams.Tests; +[Trait("Category", "UnitTest")] public class EventDataTests { [Fact] public void EmptyEventIdThrows() { var ex = Assert.Throws( - () => - new EventData(Uuid.Empty, "-", Array.Empty()) + () => new EventData(Uuid.Empty, "-", Array.Empty()) ); Assert.Equal("eventId", ex.ParamName); diff --git a/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj b/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj index dac52c701..d32b56a43 100644 --- a/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj +++ b/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj @@ -3,4 +3,7 @@ + + + \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj.DotSettings b/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj.DotSettings new file mode 100644 index 000000000..9176e378d --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/EventStore.Client.Streams.Tests.csproj.DotSettings @@ -0,0 +1,12 @@ + + False + True + False + False + True + False + True + True + True + True + False \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/EventStoreClientFixture.cs b/test/EventStore.Client.Streams.Tests/EventStoreClientFixture.cs deleted file mode 100644 index bb8c322cc..000000000 --- a/test/EventStore.Client.Streams.Tests/EventStoreClientFixture.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -public abstract class EventStoreClientFixture : EventStoreClientFixtureBase { - protected EventStoreClientFixture( - EventStoreClientSettings? settings = null, - Dictionary? env = null, bool noDefaultCredentials = false - ) - : base(settings, env, noDefaultCredentials) => - Client = new(Settings); - - public EventStoreClient Client { get; } - - protected override async Task OnServerUpAsync() => await Client.WarmUp(); - - public override async Task DisposeAsync() { - await Client.DisposeAsync(); - await base.DisposeAsync(); - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/FilterTestCase.cs b/test/EventStore.Client.Streams.Tests/FilterTestCase.cs deleted file mode 100644 index 948bdf178..000000000 --- a/test/EventStore.Client.Streams.Tests/FilterTestCase.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Reflection; - -namespace EventStore.Client.Streams.Tests; - -public static class Filters { - const string StreamNamePrefix = nameof(StreamNamePrefix); - const string StreamNameRegex = nameof(StreamNameRegex); - const string EventTypePrefix = nameof(EventTypePrefix); - const string EventTypeRegex = nameof(EventTypeRegex); - - public static readonly IEnumerable All = typeof(Filters) - .GetFields(BindingFlags.NonPublic | BindingFlags.Static) - .Where(fi => fi.IsLiteral && !fi.IsInitOnly) - .Select(fi => (string)fi.GetRawConstantValue()!); - - static readonly IDictionary, Func)> EventFilters; - - static Filters() => - EventFilters = new Dictionary, Func)> { - [StreamNamePrefix] = (StreamFilter.Prefix, (_, e) => e), - [StreamNameRegex] = (f => StreamFilter.RegularExpression(f), (_, e) => e), - [EventTypePrefix] = (EventTypeFilter.Prefix, (term, e) => new(e.EventId, term, e.Data, e.Metadata, e.ContentType)), - [EventTypeRegex] = (f => EventTypeFilter.RegularExpression(f), (term, e) => new(e.EventId, term, e.Data, e.Metadata, e.ContentType)) - }; - - public static (Func getFilter, Func prepareEvent) GetFilter(string name) => EventFilters[name]; -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/stream_metadata.cs b/test/EventStore.Client.Streams.Tests/Metadata/stream_metadata.cs similarity index 59% rename from test/EventStore.Client.Streams.Tests/stream_metadata.cs rename to test/EventStore.Client.Streams.Tests/Metadata/stream_metadata.cs index 4f57b3c86..706d5e2b6 100644 --- a/test/EventStore.Client.Streams.Tests/stream_metadata.cs +++ b/test/EventStore.Client.Streams.Tests/Metadata/stream_metadata.cs @@ -1,31 +1,28 @@ using System.Text.Json; +using Grpc.Core; -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "LongRunning")] -public class stream_metadata : IClassFixture { - readonly Fixture _fixture; - - public stream_metadata(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests.Metadata; +[Trait("Category", "Metadata")] +public class stream_metadata(ITestOutputHelper output, EventStoreFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task getting_for_an_existing_stream_and_no_metadata_exists() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents()); - var actual = await _fixture.Client.GetStreamMetadataAsync(stream); + var actual = await Fixture.Streams.GetStreamMetadataAsync(stream); Assert.Equal(StreamMetadataResult.None(stream), actual); } [Fact] public async Task empty_metadata() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); - await _fixture.Client.SetStreamMetadataAsync(stream, StreamState.NoStream, new()); + await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.NoStream, new()); - var actual = await _fixture.Client.GetStreamMetadataAsync(stream); + var actual = await Fixture.Streams.GetStreamMetadataAsync(stream); Assert.Equal(stream, actual.StreamName); Assert.Equal(StreamPosition.Start, actual.MetastreamRevision); @@ -43,7 +40,7 @@ public async Task empty_metadata() { [Fact] public async Task latest_metadata_is_returned() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); var expected = new StreamMetadata( 17, @@ -52,9 +49,9 @@ public async Task latest_metadata_is_returned() { TimeSpan.FromSeconds(0xABACABA) ); - await _fixture.Client.SetStreamMetadataAsync(stream, StreamState.NoStream, expected); + await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.NoStream, expected); - var actual = await _fixture.Client.GetStreamMetadataAsync(stream); + var actual = await Fixture.Streams.GetStreamMetadataAsync(stream); Assert.Equal(stream, actual.StreamName); Assert.False(actual.StreamDeleted); @@ -72,9 +69,9 @@ public async Task latest_metadata_is_returned() { TimeSpan.FromSeconds(0xDABACABAD) ); - await _fixture.Client.SetStreamMetadataAsync(stream, new StreamRevision(0), expected); + await Fixture.Streams.SetStreamMetadataAsync(stream, new StreamRevision(0), expected); - actual = await _fixture.Client.GetStreamMetadataAsync(stream); + actual = await Fixture.Streams.GetStreamMetadataAsync(stream); Assert.Equal(stream, actual.StreamName); Assert.False(actual.StreamDeleted); @@ -88,18 +85,18 @@ public async Task latest_metadata_is_returned() { [Fact] public async Task setting_with_wrong_expected_version_throws() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); await Assert.ThrowsAsync( () => - _fixture.Client.SetStreamMetadataAsync(stream, new StreamRevision(2), new()) + Fixture.Streams.SetStreamMetadataAsync(stream, new StreamRevision(2), new()) ); } [Fact] public async Task setting_with_wrong_expected_version_returns() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); var writeResult = - await _fixture.Client.SetStreamMetadataAsync( + await Fixture.Streams.SetStreamMetadataAsync( stream, new StreamRevision(2), new(), @@ -111,7 +108,7 @@ await _fixture.Client.SetStreamMetadataAsync( [Fact] public async Task latest_metadata_returned_stream_revision_any() { - var stream = _fixture.GetStreamName(); + var stream = Fixture.GetStreamName(); var expected = new StreamMetadata( 17, @@ -120,9 +117,9 @@ public async Task latest_metadata_returned_stream_revision_any() { TimeSpan.FromSeconds(0xABACABA) ); - await _fixture.Client.SetStreamMetadataAsync(stream, StreamState.Any, expected); + await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.Any, expected); - var actual = await _fixture.Client.GetStreamMetadataAsync(stream); + var actual = await Fixture.Streams.GetStreamMetadataAsync(stream); Assert.Equal(stream, actual.StreamName); Assert.False(actual.StreamDeleted); @@ -140,9 +137,9 @@ public async Task latest_metadata_returned_stream_revision_any() { TimeSpan.FromSeconds(0xDABACABAD) ); - await _fixture.Client.SetStreamMetadataAsync(stream, StreamState.Any, expected); + await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.Any, expected); - actual = await _fixture.Client.GetStreamMetadataAsync(stream); + actual = await Fixture.Streams.GetStreamMetadataAsync(stream); Assert.Equal(stream, actual.StreamName); Assert.False(actual.StreamDeleted); @@ -153,9 +150,45 @@ public async Task latest_metadata_returned_stream_revision_any() { Assert.Equal(expected.CacheControl, actual.Metadata.CacheControl); Assert.Equal(expected.Acl, actual.Metadata.Acl); } + + [Fact] + public async Task with_timeout_set_with_any_stream_revision_fails_when_operation_expired() { + var stream = Fixture.GetStreamName(); + var rpcException = await Assert.ThrowsAsync( + () => + Fixture.Streams.SetStreamMetadataAsync( + stream, + StreamState.Any, + new(), + deadline: TimeSpan.Zero + ) + ); + + Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); + } + + [Fact] + public async Task with_timeout_set_with_stream_revision_fails_when_operation_expired() { + var stream = Fixture.GetStreamName(); + + var rpcException = await Assert.ThrowsAsync( + () => + Fixture.Streams.SetStreamMetadataAsync( + stream, + new StreamRevision(0), + new(), + deadline: TimeSpan.Zero + ) + ); + + Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); + } - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; + [Fact] + public async Task with_timeout_get_fails_when_operation_expired() { + var stream = Fixture.GetStreamName(); + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.GetStreamMetadataAsync(stream, TimeSpan.Zero) + ); } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Read/EventBinaryData.cs b/test/EventStore.Client.Streams.Tests/Read/EventBinaryData.cs new file mode 100644 index 000000000..923a40cd1 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Read/EventBinaryData.cs @@ -0,0 +1,33 @@ +namespace EventStore.Client.Streams.Tests.Read; + +public readonly record struct EventBinaryData(Uuid Id, byte[] Data, byte[] Metadata) { + public bool Equals(EventBinaryData other) => + Id.Equals(other.Id) + && Data.SequenceEqual(other.Data) + && Metadata.SequenceEqual(other.Metadata); + + public override int GetHashCode() => System.HashCode.Combine(Id, Data, Metadata); +} + +public static class EventBinaryDataConverters { + public static EventBinaryData ToBinaryData(this EventData source) => + new(source.EventId, source.Data.ToArray(), source.Metadata.ToArray()); + + public static EventBinaryData ToBinaryData(this EventRecord source) => + new(source.EventId, source.Data.ToArray(), source.Metadata.ToArray()); + + public static EventBinaryData ToBinaryData(this ResolvedEvent source) => + source.Event.ToBinaryData(); + + public static EventBinaryData[] ToBinaryData(this IEnumerable source) => + source.Select(x => x.ToBinaryData()).ToArray(); + + public static EventBinaryData[] ToBinaryData(this IEnumerable source) => + source.Select(x => x.ToBinaryData()).ToArray(); + + public static EventBinaryData[] ToBinaryData(this IEnumerable source) => + source.Select(x => x.ToBinaryData()).ToArray(); + + public static ValueTask ToBinaryData(this IAsyncEnumerable source) => + source.DefaultIfEmpty().Select(x => x.ToBinaryData()).ToArrayAsync(); +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Read/EventDataComparer.cs b/test/EventStore.Client.Streams.Tests/Read/EventDataComparer.cs new file mode 100644 index 000000000..cd9c6f41a --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Read/EventDataComparer.cs @@ -0,0 +1,21 @@ +namespace EventStore.Client.Streams.Tests.Read; + +static class EventDataComparer { + public static bool Equal(EventData expected, EventRecord actual) { + if (expected.EventId != actual.EventId) + return false; + + if (expected.Type != actual.EventType) + return false; + + return expected.Data.ToArray().SequenceEqual(actual.Data.ToArray()) + && expected.Metadata.ToArray().SequenceEqual(actual.Metadata.ToArray()); + } + + public static bool Equal(EventData[] expected, EventRecord[] actual) { + if (expected.Length != actual.Length) + return false; + + return !expected.Where((t, i) => !Equal(t, actual[i])).Any(); + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Read/ReadAllEventsFixture.cs b/test/EventStore.Client.Streams.Tests/Read/ReadAllEventsFixture.cs new file mode 100644 index 000000000..297a79b70 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Read/ReadAllEventsFixture.cs @@ -0,0 +1,41 @@ +namespace EventStore.Client.Streams.Tests.Read; + +public class ReadAllEventsFixture : EventStoreFixture { + public ReadAllEventsFixture() { + OnSetup = async () => { + _ = await Streams.SetStreamMetadataAsync( + SystemStreams.AllStream, + StreamState.NoStream, + new(acl: new(SystemRoles.All)), + userCredentials: TestCredentials.Root + ); + + Events = Enumerable + .Concat( + CreateTestEvents(20), + CreateTestEvents(2, metadataSize: 1_000_000) + ) + .ToArray(); + + ExpectedStreamName = GetStreamName(); + + await Streams.AppendToStreamAsync(ExpectedStreamName, StreamState.NoStream, Events); + + ExpectedEvents = Events.ToBinaryData(); + ExpectedEventsReversed = ExpectedEvents.Reverse().ToArray(); + + ExpectedFirstEvent = ExpectedEvents.First(); + ExpectedLastEvent = ExpectedEvents.Last(); + }; + } + + public string ExpectedStreamName { get; private set; } = null!; + + public EventData[] Events { get; private set; } = Array.Empty(); + + public EventBinaryData[] ExpectedEvents { get; private set; } = Array.Empty(); + public EventBinaryData[] ExpectedEventsReversed { get; private set; } = Array.Empty(); + + public EventBinaryData ExpectedFirstEvent { get; private set; } + public EventBinaryData ExpectedLastEvent { get; private set; } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Read/read_all_events_backward.cs b/test/EventStore.Client.Streams.Tests/Read/read_all_events_backward.cs new file mode 100644 index 000000000..63135ec82 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Read/read_all_events_backward.cs @@ -0,0 +1,83 @@ +using Grpc.Core; + +namespace EventStore.Client.Streams.Tests.Read; + +[Trait("Category", "Target:All")] +[Trait("Category", "Operation:Read")] +[Trait("Category", "Operation:Read:Backwards")] +[Trait("Category", "Database:Dedicated")] +public class read_all_events_backward(ITestOutputHelper output, ReadAllEventsFixture fixture) : EventStoreTests(output, fixture) { + [Fact] + public async Task return_empty_if_reading_from_start() { + var result = await Fixture.Streams.ReadAllAsync(Direction.Backwards, Position.Start, 1).CountAsync(); + result.ShouldBe(0); + } + + [Fact] + public async Task return_partial_slice_if_not_enough_events() { + var count = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start).CountAsync(); + var sliceSize = count * 2; + var result = await Fixture.Streams.ReadAllAsync(Direction.Backwards, Position.End, sliceSize).ToArrayAsync(); + result.Length.ShouldBeLessThan(sliceSize); + } + + [Fact] + public async Task return_events_in_reversed_order_compared_to_written() { + // TODO SS: this test must be fixed to deterministically read the expected events regardless of how many already exist. reading all for now... + var result = await Fixture.Streams + .ReadAllAsync(Direction.Backwards, Position.End) + .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) + .ToBinaryData(); + + result.ShouldBe(Fixture.ExpectedEventsReversed); + } + + [Fact] + public async Task return_single_event() { + var result = await Fixture.Streams + .ReadAllAsync(Direction.Backwards, Position.End, 1) + .ToArrayAsync(); + + result.ShouldHaveSingleItem(); + } + + [Fact] + public async Task max_count_is_respected() { + var maxCount = Fixture.ExpectedEvents.Length / 2; + var result = await Fixture.Streams + .ReadAllAsync(Direction.Backwards, Position.End, maxCount) + .Take(Fixture.ExpectedEvents.Length) + .ToArrayAsync(); + + result.Length.ShouldBe(maxCount); + } + + [Fact] + public async Task stream_found() { + var count = await Fixture.Streams + .ReadAllAsync(Direction.Backwards, Position.End) + .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) + .CountAsync(); + + count.ShouldBe(Fixture.ExpectedEvents.Length); + } + + [Fact] + public async Task with_timeout_fails_when_operation_expired() { + var ex = await Fixture.Streams + .ReadAllAsync(Direction.Backwards, Position.Start, 1, false, TimeSpan.Zero) + .ToArrayAsync() + .AsTask().ShouldThrowAsync(); + + ex.StatusCode.ShouldBe(StatusCode.DeadlineExceeded); + } + + [Fact(Skip = "Not Implemented")] + public Task be_able_to_read_all_one_by_one_until_end_of_stream() => throw new NotImplementedException(); + + [Fact(Skip = "Not Implemented")] + public Task be_able_to_read_events_slice_at_time() => throw new NotImplementedException(); + + [Fact(Skip = "Not Implemented")] + public Task when_got_int_max_value_as_maxcount_should_throw() => throw new NotImplementedException(); +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Read/read_all_events_forward.cs b/test/EventStore.Client.Streams.Tests/Read/read_all_events_forward.cs new file mode 100644 index 000000000..ab93da29a --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Read/read_all_events_forward.cs @@ -0,0 +1,150 @@ +using System.Text; + +namespace EventStore.Client.Streams.Tests.Read; + +[Trait("Category", "Target:All")] +[Trait("Category", "Operation:Read")] +[Trait("Category", "Operation:Read:Forwards")] +[Trait("Category", "Database:Dedicated")] +public class read_all_events_forward(ITestOutputHelper output, ReadAllEventsFixture fixture) : EventStoreTests(output, fixture) { + [Fact] + public async Task return_empty_if_reading_from_end() { + var result = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.End, 1).CountAsync(); + result.ShouldBe(0); + } + + [Fact] + public async Task return_partial_slice_if_not_enough_events() { + var sliceSize = Fixture.ExpectedEvents.Length * 2; + var result = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start, sliceSize).ToArrayAsync(); + result.Length.ShouldBeLessThan(sliceSize); + } + + [Fact] + public async Task return_events_in_correct_order_compared_to_written() { + // TODO SS: this test must be fixed to deterministically read the expected events regardless of how many already exist. reading all for now... + var result = await Fixture.Streams + .ReadAllAsync(Direction.Forwards, Position.Start) + .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) + .ToBinaryData(); + + result.ShouldBe(Fixture.ExpectedEvents); + } + + [Fact] + public async Task return_single_event() { + var result = await Fixture.Streams + .ReadAllAsync(Direction.Forwards, Position.Start, 1) + .ToArrayAsync(); + + result.ShouldHaveSingleItem(); + } + + [Fact] + public async Task max_count_is_respected() { + var maxCount = Fixture.ExpectedEvents.Length / 2; + var result = await Fixture.Streams + .ReadAllAsync(Direction.Forwards, Position.Start, maxCount) + .Take(Fixture.ExpectedEvents.Length) + .ToArrayAsync(); + + result.Length.ShouldBe(maxCount); + } + + [Fact] + public async Task reads_all_events_by_default() { + var count = await Fixture.Streams + .ReadAllAsync(Direction.Forwards, Position.Start) + .CountAsync(); + + Assert.True(count >= Fixture.ExpectedEvents.Length); + } + + [Fact] + public async Task stream_found() { + var count = await Fixture.Streams + .ReadAllAsync(Direction.Forwards, Position.Start) + .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) + .CountAsync(); + + count.ShouldBe(Fixture.ExpectedEvents.Length); + } + + [Fact] + public async Task with_linkto_passed_max_count_one_event_is_read() { + const string deletedStream = nameof(deletedStream); + const string linkedStream = nameof(linkedStream); + + await Fixture.Streams.AppendToStreamAsync(deletedStream, StreamState.Any, Fixture.CreateTestEvents()); + await Fixture.Streams.SetStreamMetadataAsync( + deletedStream, + StreamState.Any, + new(2) + ); + + await Fixture.Streams.AppendToStreamAsync(deletedStream, StreamState.Any, Fixture.CreateTestEvents()); + await Fixture.Streams.AppendToStreamAsync(deletedStream, StreamState.Any, Fixture.CreateTestEvents()); + await Fixture.Streams.AppendToStreamAsync( + linkedStream, + StreamState.Any, + new[] { + new EventData( + Uuid.NewUuid(), + SystemEventTypes.LinkTo, + Encoding.UTF8.GetBytes($"0@{deletedStream}"), + Array.Empty(), + Constants.Metadata.ContentTypes.ApplicationOctetStream + ) + } + ); + + var events = await Fixture.Streams.ReadStreamAsync( + Direction.Forwards, + linkedStream, + StreamPosition.Start, + resolveLinkTos: true + ) + .ToArrayAsync(); + + Assert.Single(events); + } + + [Fact] + public async Task enumeration_all_referencing_messages_twice_does_not_throw() { + var result = Fixture.Streams.ReadAllAsync( + Direction.Forwards, + Position.Start, + 32, + userCredentials: TestCredentials.Root + ); + + _ = result.Messages; + await result.Messages.ToArrayAsync(); + } + + [Fact] + public async Task enumeration_all_enumerating_messages_twice_throws() { + var result = Fixture.Streams.ReadAllAsync( + Direction.Forwards, + Position.Start, + 32, + userCredentials: TestCredentials.Root + ); + + await result.Messages.ToArrayAsync(); + + await Assert.ThrowsAsync( + async () => + await result.Messages.ToArrayAsync() + ); + } + + [Fact(Skip = "Not Implemented")] + public Task be_able_to_read_all_one_by_one_until_end_of_stream() => throw new NotImplementedException(); + + [Fact(Skip = "Not Implemented")] + public Task be_able_to_read_events_slice_at_time() => throw new NotImplementedException(); + + [Fact(Skip = "Not Implemented")] + public Task when_got_int_max_value_as_maxcount_should_throw() => throw new NotImplementedException(); +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Read/read_stream_backward.cs b/test/EventStore.Client.Streams.Tests/Read/read_stream_backward.cs new file mode 100644 index 000000000..4101ef3b6 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Read/read_stream_backward.cs @@ -0,0 +1,277 @@ +using Grpc.Core; + +namespace EventStore.Client.Streams.Tests.Read; + +[Trait("Category", "Target:Stream")] +[Trait("Category", "Operation:Read")] +[Trait("Category", "Operation:Read:Backwards")] +public class read_stream_backward(ITestOutputHelper output, EventStoreFixture fixture) : EventStoreTests(output, fixture) { + [Theory] + [InlineData(0)] + public async Task count_le_equal_zero_throws(long maxCount) { + var stream = Fixture.GetStreamName(); + + var ex = await Assert.ThrowsAsync( + () => + Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.Start, maxCount) + .ToArrayAsync().AsTask() + ); + + Assert.Equal(nameof(maxCount), ex.ParamName); + } + + [Fact] + public async Task stream_does_not_exist_throws() { + var stream = Fixture.GetStreamName(); + + var ex = await Assert.ThrowsAsync( + () => Fixture.Streams + .ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) + .ToArrayAsync().AsTask() + ); + + Assert.Equal(stream, ex.Stream); + } + + [Fact] + public async Task stream_does_not_exist_can_be_checked() { + var stream = Fixture.GetStreamName(); + + var result = Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1); + + var state = await result.ReadState; + Assert.Equal(ReadState.StreamNotFound, state); + } + + [Fact] + public async Task stream_deleted_throws() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream); + + var ex = await Assert.ThrowsAsync( + () => Fixture.Streams + .ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) + .ToArrayAsync().AsTask() + ); + + Assert.Equal(stream, ex.Stream); + } + + [Theory] + [InlineData("small_events", 10, 1)] + [InlineData("large_events", 2, 1_000_000)] + public async Task returns_events_in_reversed_order(string suffix, int count, int metadataSize) { + var stream = $"{Fixture.GetStreamName()}_{suffix}"; + + var expected = Fixture.CreateTestEvents(count, metadataSize: metadataSize).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); + + var actual = await Fixture.Streams + .ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, expected.Length) + .Select(x => x.Event).ToArrayAsync(); + + Assert.True( + EventDataComparer.Equal( + expected.Reverse().ToArray(), + actual + ) + ); + } + + [Fact] + public async Task be_able_to_read_single_event_from_arbitrary_position() { + var stream = Fixture.GetStreamName(); + var events = Fixture.CreateTestEvents(10).ToArray(); + + var expected = events[7]; + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, new(7), 1) + .Select(x => x.Event) + .SingleAsync(); + + Assert.True(EventDataComparer.Equal(expected, actual)); + } + + [Fact] + public async Task be_able_to_read_from_arbitrary_position() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(10).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, new(3), 2) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.True(EventDataComparer.Equal(events.Skip(2).Take(2).Reverse().ToArray(), actual)); + } + + [Fact] + public async Task be_able_to_read_first_event() { + var stream = Fixture.GetStreamName(); + + var testEvents = Fixture.CreateTestEvents(10).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); + + var events = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.Start, 1) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.Single(events); + Assert.True(EventDataComparer.Equal(testEvents[0], events[0])); + } + + [Fact] + public async Task be_able_to_read_last_event() { + var stream = Fixture.GetStreamName(); + var testEvents = Fixture.CreateTestEvents(10).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); + + var events = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.Single(events); + Assert.True(EventDataComparer.Equal(testEvents[^1], events[0])); + } + + [Fact] + public async Task max_count_is_respected() { + const int count = 20; + const long maxCount = count / 2; + + var streamName = Fixture.GetStreamName(); + + await Fixture.Streams.AppendToStreamAsync( + streamName, + StreamState.NoStream, + Fixture.CreateTestEvents(count) + ); + + var events = await Fixture.Streams + .ReadStreamAsync(Direction.Backwards, streamName, StreamPosition.End, maxCount) + .Take(count) + .ToArrayAsync(); + + Assert.Equal(maxCount, events.Length); + } + + [Fact] + public async Task populates_log_position_of_event() { + if (EventStoreTestServer.Version.Major < 22) + return; + + var stream = Fixture.GetStreamName(); + var events = Fixture.CreateTestEvents(1).ToArray(); + + var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.Single(actual); + Assert.Equal(writeResult.LogPosition.PreparePosition, writeResult.LogPosition.CommitPosition); + Assert.Equal(writeResult.LogPosition, actual.First().Position); + } + + [Fact] + public async Task with_timeout_fails_when_operation_expired() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamRevision.None, Fixture.CreateTestEvents()); + + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams + .ReadStreamAsync( + Direction.Backwards, + stream, + StreamPosition.End, + 1, + false, + TimeSpan.Zero + ) + .ToArrayAsync().AsTask() + ); + + Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); + } + + [Fact] + public async Task enumeration_referencing_messages_twice_does_not_throw() { + var result = Fixture.Streams.ReadStreamAsync( + Direction.Forwards, + "$users", + StreamPosition.Start, + 32, + userCredentials: TestCredentials.Root + ); + + _ = result.Messages; + await result.Messages.ToArrayAsync(); + } + + [Fact] + public async Task enumeration_enumerating_messages_twice_throws() { + var result = Fixture.Streams.ReadStreamAsync( + Direction.Forwards, + "$users", + StreamPosition.Start, + 32, + userCredentials: TestCredentials.Root + ); + + await result.Messages.ToArrayAsync(); + + await Assert.ThrowsAsync( + async () => + await result.Messages.ToArrayAsync() + ); + } + + [Fact] + public async Task stream_not_found() { + var result = await Fixture.Streams.ReadStreamAsync( + Direction.Backwards, + Fixture.GetStreamName(), + StreamPosition.End + ).Messages.SingleAsync(); + + Assert.Equal(StreamMessage.NotFound.Instance, result); + } + + [Fact] + public async Task stream_found() { + const int eventCount = 32; + + var events = Fixture.CreateTestEvents(eventCount).ToArray(); + + var streamName = Fixture.GetStreamName(); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, events); + + var result = await Fixture.Streams.ReadStreamAsync( + Direction.Backwards, + streamName, + StreamPosition.End + ).Messages.ToArrayAsync(); + + Assert.Equal( + eventCount + (Fixture.EventStoreHasLastStreamPosition ? 2 : 1), + result.Length + ); + + Assert.Equal(StreamMessage.Ok.Instance, result[0]); + Assert.Equal(eventCount, result.OfType().Count()); + + if (Fixture.EventStoreHasLastStreamPosition) + Assert.Equal(new StreamMessage.LastStreamPosition(new(31)), result[^1]); + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Read/read_stream_events_linked_to_deleted_stream.cs b/test/EventStore.Client.Streams.Tests/Read/read_stream_events_linked_to_deleted_stream.cs new file mode 100644 index 000000000..ba11b75f0 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Read/read_stream_events_linked_to_deleted_stream.cs @@ -0,0 +1,73 @@ +using System.Text; + +namespace EventStore.Client.Streams.Tests.Read; + +[Trait("Category", "Target:Stream")] +public abstract class read_stream_events_linked_to_deleted_stream(ReadEventsLinkedToDeletedStreamFixture fixture) { + ReadEventsLinkedToDeletedStreamFixture Fixture { get; } = fixture; + + [Fact] + public void one_event_is_read() => Assert.Single(Fixture.Events ?? Array.Empty()); + + [Fact] + public void the_linked_event_is_not_resolved() => Assert.Null(Fixture.Events![0].Event); + + [Fact] + public void the_link_event_is_included() => Assert.NotNull(Fixture.Events![0].OriginalEvent); + + [Fact] + public void the_event_is_not_resolved() => Assert.False(Fixture.Events![0].IsResolved); + + [Trait("Category", "Operation:Read")] + [Trait("Category", "Operation:Read:Forwards")] + public class @forwards(forwards.CustomFixture fixture) + : read_stream_events_linked_to_deleted_stream(fixture), IClassFixture { + + public class CustomFixture() : ReadEventsLinkedToDeletedStreamFixture(Direction.Forwards); + } + + [Trait("Category", "Operation:Read")] + [Trait("Category", "Operation:Read:Backwards")] + public class @backwards(backwards.CustomFixture fixture) + : read_stream_events_linked_to_deleted_stream(fixture), IClassFixture { + + public class CustomFixture() : ReadEventsLinkedToDeletedStreamFixture(Direction.Backwards); + } +} + +public abstract class ReadEventsLinkedToDeletedStreamFixture : EventStoreFixture { + const string DeletedStream = nameof(DeletedStream); + const string LinkedStream = nameof(LinkedStream); + + protected ReadEventsLinkedToDeletedStreamFixture(Direction direction) { + OnSetup = async () => { + await Streams.AppendToStreamAsync(DeletedStream, StreamState.Any, CreateTestEvents()); + + await Streams.AppendToStreamAsync( + LinkedStream, + StreamState.Any, + new[] { + new EventData( + Uuid.NewUuid(), + SystemEventTypes.LinkTo, + Encoding.UTF8.GetBytes($"0@{DeletedStream}"), + Array.Empty(), + Constants.Metadata.ContentTypes.ApplicationOctetStream + ) + } + ); + + await Streams.DeleteAsync(DeletedStream, StreamState.Any); + + Events = await Streams.ReadStreamAsync( + direction, + LinkedStream, + StreamPosition.Start, + 1, + true + ).ToArrayAsync(); + }; + } + + public ResolvedEvent[]? Events { get; private set; } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Read/read_stream_forward.cs b/test/EventStore.Client.Streams.Tests/Read/read_stream_forward.cs new file mode 100644 index 000000000..b8571b121 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Read/read_stream_forward.cs @@ -0,0 +1,266 @@ +namespace EventStore.Client.Streams.Tests.Read; + +[Trait("Category", "Target:Stream")] +[Trait("Category", "Operation:Read")] +[Trait("Category", "Operation:Read:Forwards")] +public class read_stream_forward(ITestOutputHelper output, EventStoreFixture fixture) : EventStoreTests(output, fixture) { + [Theory] + [InlineData(0)] + public async Task count_le_equal_zero_throws(long maxCount) { + var stream = Fixture.GetStreamName(); + + var ex = await Assert.ThrowsAsync( + () => + Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, maxCount) + .ToArrayAsync().AsTask() + ); + + Assert.Equal(nameof(maxCount), ex.ParamName); + } + + [Fact] + public async Task stream_does_not_exist_throws() { + var stream = Fixture.GetStreamName(); + + var ex = await Assert.ThrowsAsync( + () => Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) + .ToArrayAsync().AsTask() + ); + + Assert.Equal(stream, ex.Stream); + } + + [Fact] + public async Task stream_does_not_exist_can_be_checked() { + var stream = Fixture.GetStreamName(); + + var result = Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1); + + var state = await result.ReadState; + Assert.Equal(ReadState.StreamNotFound, state); + } + + [Fact] + public async Task stream_deleted_throws() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream); + + var ex = await Assert.ThrowsAsync( + () => Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) + .ToArrayAsync().AsTask() + ); + + Assert.Equal(stream, ex.Stream); + } + + [Theory] + [InlineData("small_events", 10, 1)] + [InlineData("large_events", 2, 1_000_000)] + public async Task returns_events_in_order(string suffix, int count, int metadataSize) { + var stream = $"{Fixture.GetStreamName()}_{suffix}"; + + var expected = Fixture.CreateTestEvents(count, metadataSize: metadataSize).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); + + var actual = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, expected.Length) + .Select(x => x.Event).ToArrayAsync(); + + Assert.True(EventDataComparer.Equal(expected, actual)); + } + + [Fact] + public async Task be_able_to_read_single_event_from_arbitrary_position() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(10).ToArray(); + + var expected = events[7]; + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, new(7), 1) + .Select(x => x.Event) + .SingleAsync(); + + Assert.True(EventDataComparer.Equal(expected, actual)); + } + + [Fact] + public async Task be_able_to_read_from_arbitrary_position() { + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(10).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, new(3), 2) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.True(EventDataComparer.Equal(events.Skip(3).Take(2).ToArray(), actual)); + } + + [Fact] + public async Task be_able_to_read_first_event() { + var stream = Fixture.GetStreamName(); + + var testEvents = Fixture.CreateTestEvents(10).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); + + var events = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.Single(events); + Assert.True(EventDataComparer.Equal(testEvents[0], events[0])); + } + + [Fact] + public async Task max_count_is_respected() { + var streamName = Fixture.GetStreamName(); + const int count = 20; + const long maxCount = count / 2; + + await Fixture.Streams.AppendToStreamAsync( + streamName, + StreamState.NoStream, + Fixture.CreateTestEvents(count) + ); + + var events = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start, maxCount) + .Take(count) + .ToArrayAsync(); + + Assert.Equal(maxCount, events.Length); + } + + [Fact] + public async Task reads_all_events_by_default() { + var streamName = Fixture.GetStreamName(); + const int maxCount = 200; + await Fixture.Streams.AppendToStreamAsync( + streamName, + StreamState.NoStream, + Fixture.CreateTestEvents(maxCount) + ); + + var count = await Fixture.Streams + .ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start) + .CountAsync(); + + Assert.True(count == maxCount); + } + + [Fact] + public async Task populates_log_position_of_event() { + if (EventStoreTestServer.Version.Major < 22) + return; + + var stream = Fixture.GetStreamName(); + + var events = Fixture.CreateTestEvents(1).ToArray(); + + var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); + + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.Single(actual); + Assert.Equal(writeResult.LogPosition.PreparePosition, writeResult.LogPosition.CommitPosition); + Assert.Equal(writeResult.LogPosition, actual.First().Position); + } + + [Fact] + public async Task stream_not_found() { + var result = await Fixture.Streams.ReadStreamAsync( + Direction.Forwards, + Fixture.GetStreamName(), + StreamPosition.Start + ).Messages.SingleAsync(); + + Assert.Equal(StreamMessage.NotFound.Instance, result); + } + + [Fact] + public async Task stream_found() { + const int eventCount = 64; + + var events = Fixture.CreateTestEvents(eventCount).ToArray(); + + var streamName = Fixture.GetStreamName(); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, events); + + var result = await Fixture.Streams.ReadStreamAsync( + Direction.Forwards, + streamName, + StreamPosition.Start + ).Messages.ToArrayAsync(); + + Assert.Equal( + eventCount + (Fixture.EventStoreHasLastStreamPosition ? 2 : 1), + result.Length + ); + + Assert.Equal(StreamMessage.Ok.Instance, result[0]); + Assert.Equal(eventCount, result.OfType().Count()); + var first = Assert.IsType(result[1]); + Assert.Equal(new(0), first.ResolvedEvent.OriginalEventNumber); + var last = Assert.IsType(result[Fixture.EventStoreHasLastStreamPosition ? ^2 : ^1]); + Assert.Equal(new((ulong)eventCount - 1), last.ResolvedEvent.OriginalEventNumber); + + if (Fixture.EventStoreHasLastStreamPosition) + Assert.Equal( + new StreamMessage.LastStreamPosition(new((ulong)eventCount - 1)), + result[^1] + ); + } + + [Fact] + public async Task stream_found_truncated() { + const int eventCount = 64; + + var events = Fixture.CreateTestEvents(eventCount).ToArray(); + + var streamName = Fixture.GetStreamName(); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, events); + + await Fixture.Streams.SetStreamMetadataAsync( + streamName, + StreamState.Any, + new(truncateBefore: new StreamPosition(32)) + ); + + var result = await Fixture.Streams.ReadStreamAsync( + Direction.Forwards, + streamName, + StreamPosition.Start + ).Messages.ToArrayAsync(); + + Assert.Equal( + eventCount - 32 + (Fixture.EventStoreHasLastStreamPosition ? 3 : 1), + result.Length + ); + + Assert.Equal(StreamMessage.Ok.Instance, result[0]); + + if (Fixture.EventStoreHasLastStreamPosition) + Assert.Equal(new StreamMessage.FirstStreamPosition(new(32)), result[1]); + + Assert.Equal(32, result.OfType().Count()); + + if (Fixture.EventStoreHasLastStreamPosition) + Assert.Equal( + new StreamMessage.LastStreamPosition(new((ulong)eventCount - 1)), + result[^1] + ); + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Read/read_stream_when_having_max_count_set_for_stream.cs b/test/EventStore.Client.Streams.Tests/Read/read_stream_when_having_max_count_set_for_stream.cs new file mode 100644 index 000000000..0142ebd9a --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Read/read_stream_when_having_max_count_set_for_stream.cs @@ -0,0 +1,121 @@ +namespace EventStore.Client.Streams.Tests.Read; + +[Trait("Category", "Target:Stream")] +[Trait("Category", "Operation:Read")] +public class read_stream_when_having_max_count_set_for_stream (ITestOutputHelper output, EventStoreFixture fixture) : EventStoreTests(output, fixture) { + [Fact] + public async Task read_stream_forwards_respects_max_count() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.NoStream, new(3)); + + var expected = Fixture.CreateTestEvents(5).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); + + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 100) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.Equal(3, actual.Length); + Assert.True(EventDataComparer.Equal(expected.Skip(2).ToArray(), actual)); + } + + [Fact] + public async Task read_stream_backwards_respects_max_count() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.NoStream, new(3)); + + var expected = Fixture.CreateTestEvents(5).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); + + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 100) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.Equal(3, actual.Length); + Assert.True(EventDataComparer.Equal(expected.Skip(2).Reverse().ToArray(), actual)); + } + + [Fact] + public async Task after_setting_less_strict_max_count_read_stream_forward_reads_more_events() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.NoStream, new(3)); + + var expected = Fixture.CreateTestEvents(5).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); + + await Fixture.Streams.SetStreamMetadataAsync(stream, new StreamRevision(0), new(4)); + + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 100) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.Equal(4, actual.Length); + Assert.True(EventDataComparer.Equal(expected.Skip(1).ToArray(), actual)); + } + + [Fact] + public async Task after_setting_more_strict_max_count_read_stream_forward_reads_less_events() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.NoStream, new(3)); + + var expected = Fixture.CreateTestEvents(5).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); + + await Fixture.Streams.SetStreamMetadataAsync(stream, new StreamRevision(0), new(2)); + + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 100) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.Equal(2, actual.Length); + Assert.True(EventDataComparer.Equal(expected.Skip(3).ToArray(), actual)); + } + + [Fact] + public async Task after_setting_less_strict_max_count_read_stream_backwards_reads_more_events() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.NoStream, new(3)); + + var expected = Fixture.CreateTestEvents(5).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); + + await Fixture.Streams.SetStreamMetadataAsync(stream, new StreamRevision(0), new(4)); + + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 100) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.Equal(4, actual.Length); + Assert.True(EventDataComparer.Equal(expected.Skip(1).Reverse().ToArray(), actual)); + } + + [Fact] + public async Task after_setting_more_strict_max_count_read_stream_backwards_reads_less_events() { + var stream = Fixture.GetStreamName(); + + await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.NoStream, new(3)); + + var expected = Fixture.CreateTestEvents(5).ToArray(); + + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); + + await Fixture.Streams.SetStreamMetadataAsync(stream, new StreamRevision(0), new(2)); + + var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 100) + .Select(x => x.Event) + .ToArrayAsync(); + + Assert.Equal(2, actual.Length); + Assert.True(EventDataComparer.Equal(expected.Skip(3).Reverse().ToArray(), actual)); + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/SecurityFixture.cs b/test/EventStore.Client.Streams.Tests/Security/SecurityFixture.cs index 072abbead..c472581bf 100644 --- a/test/EventStore.Client.Streams.Tests/Security/SecurityFixture.cs +++ b/test/EventStore.Client.Streams.Tests/Security/SecurityFixture.cs @@ -1,8 +1,8 @@ using System.Runtime.CompilerServices; -namespace EventStore.Client.Streams.Tests.Security; +namespace EventStore.Client.Streams.Tests; -public abstract class SecurityFixture : EventStoreClientFixture { +public class SecurityFixture : EventStoreFixture { public const string NoAclStream = nameof(NoAclStream); public const string ReadStream = nameof(ReadStream); public const string WriteStream = nameof(WriteStream); @@ -10,89 +10,87 @@ public abstract class SecurityFixture : EventStoreClientFixture { public const string MetaWriteStream = nameof(MetaWriteStream); public const string AllStream = SystemStreams.AllStream; public const string NormalAllStream = nameof(NormalAllStream); - public const string SystemAllStream = "$" + nameof(SystemAllStream); - public const string SystemAdminStream = "$" + nameof(SystemAdminStream); - public const string SystemAclStream = "$" + nameof(SystemAclStream); - const int TimeoutMs = 1000; - - protected SecurityFixture() : base(noDefaultCredentials: true) => UserManagementClient = new(Settings); - - public EventStoreUserManagementClient UserManagementClient { get; } - - protected override async Task OnServerUpAsync() { - await base.OnServerUpAsync(); - - await UserManagementClient.WarmUp(); - - await UserManagementClient.CreateUserWithRetry( - TestCredentials.TestUser1.Username!, - nameof(TestCredentials.TestUser1), - Array.Empty(), - TestCredentials.TestUser1.Password!, - TestCredentials.Root - ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - - await UserManagementClient.CreateUserWithRetry( - TestCredentials.TestUser2.Username!, - nameof(TestCredentials.TestUser2), - Array.Empty(), - TestCredentials.TestUser2.Password!, - TestCredentials.Root - ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - - await UserManagementClient.CreateUserWithRetry( - TestCredentials.TestAdmin.Username!, - nameof(TestCredentials.TestAdmin), - new[] { SystemRoles.Admins }, - TestCredentials.TestAdmin.Password!, - TestCredentials.Root - ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); + public const string SystemAllStream = $"${nameof(SystemAllStream)}"; + public const string SystemAdminStream = $"${nameof(SystemAdminStream)}"; + public const string SystemAclStream = $"${nameof(SystemAclStream)}"; + + const int TimeoutMs = 1000; + + public SecurityFixture() : base(x => x.WithoutDefaultCredentials()) { + OnSetup = async () => { + await Users.CreateUserWithRetry( + TestCredentials.TestUser1.Username!, + nameof(TestCredentials.TestUser1), + Array.Empty(), + TestCredentials.TestUser1.Password!, + TestCredentials.Root + ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); + + await Users.CreateUserWithRetry( + TestCredentials.TestUser2.Username!, + nameof(TestCredentials.TestUser2), + Array.Empty(), + TestCredentials.TestUser2.Password!, + TestCredentials.Root + ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); + + await Users.CreateUserWithRetry( + TestCredentials.TestAdmin.Username!, + nameof(TestCredentials.TestAdmin), + new[] { SystemRoles.Admins }, + TestCredentials.TestAdmin.Password!, + TestCredentials.Root + ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); + + await Given(); + await When(); + }; } - protected override async Task Given() { - await Client.SetStreamMetadataAsync( + protected virtual async Task Given() { + await Streams.SetStreamMetadataAsync( NoAclStream, StreamState.NoStream, new(), userCredentials: TestCredentials.TestAdmin ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( ReadStream, StreamState.NoStream, new(acl: new(TestCredentials.TestUser1.Username)), userCredentials: TestCredentials.TestAdmin ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( WriteStream, StreamState.NoStream, new(acl: new(writeRole: TestCredentials.TestUser1.Username)), userCredentials: TestCredentials.TestAdmin ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( MetaReadStream, StreamState.NoStream, new(acl: new(metaReadRole: TestCredentials.TestUser1.Username)), userCredentials: TestCredentials.TestAdmin ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( MetaWriteStream, StreamState.NoStream, new(acl: new(metaWriteRole: TestCredentials.TestUser1.Username)), userCredentials: TestCredentials.TestAdmin ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( AllStream, StreamState.Any, new(acl: new(TestCredentials.TestUser1.Username)), userCredentials: TestCredentials.TestAdmin ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( SystemAclStream, StreamState.NoStream, new( @@ -106,7 +104,7 @@ await Client.SetStreamMetadataAsync( userCredentials: TestCredentials.TestAdmin ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( SystemAdminStream, StreamState.NoStream, new( @@ -120,7 +118,7 @@ await Client.SetStreamMetadataAsync( userCredentials: TestCredentials.TestAdmin ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( NormalAllStream, StreamState.NoStream, new( @@ -134,7 +132,7 @@ await Client.SetStreamMetadataAsync( userCredentials: TestCredentials.TestAdmin ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( SystemAllStream, StreamState.NoStream, new( @@ -149,8 +147,10 @@ await Client.SetStreamMetadataAsync( ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); } + protected virtual Task When() => Task.CompletedTask; + public Task ReadEvent(string streamId, UserCredentials? userCredentials = default) => - Client.ReadStreamAsync( + Streams.ReadStreamAsync( Direction.Forwards, streamId, StreamPosition.Start, @@ -163,7 +163,7 @@ public Task ReadEvent(string streamId, UserCredentials? userCredentials = defaul .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task ReadStreamForward(string streamId, UserCredentials? userCredentials = default) => - Client.ReadStreamAsync( + Streams.ReadStreamAsync( Direction.Forwards, streamId, StreamPosition.Start, @@ -176,7 +176,7 @@ public Task ReadStreamForward(string streamId, UserCredentials? userCredentials .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task ReadStreamBackward(string streamId, UserCredentials? userCredentials = default) => - Client.ReadStreamAsync( + Streams.ReadStreamAsync( Direction.Backwards, streamId, StreamPosition.Start, @@ -189,7 +189,7 @@ public Task ReadStreamBackward(string streamId, UserCredentials? userCredentials .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task AppendStream(string streamId, UserCredentials? userCredentials = default) => - Client.AppendToStreamAsync( + Streams.AppendToStreamAsync( streamId, StreamState.Any, CreateTestEvents(3), @@ -198,7 +198,7 @@ public Task AppendStream(string streamId, UserCredentials? userCre .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task ReadAllForward(UserCredentials? userCredentials = default) => - Client.ReadAllAsync( + Streams.ReadAllAsync( Direction.Forwards, Position.Start, 1, @@ -210,7 +210,8 @@ public Task ReadAllForward(UserCredentials? userCredentials = default) => .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task ReadAllBackward(UserCredentials? userCredentials = default) => - Client.ReadAllAsync( + Streams + .ReadAllAsync( Direction.Backwards, Position.End, 1, @@ -222,14 +223,11 @@ public Task ReadAllBackward(UserCredentials? userCredentials = default) => .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task ReadMeta(string streamId, UserCredentials? userCredentials = default) => - Client.GetStreamMetadataAsync(streamId, userCredentials: userCredentials) + Streams.GetStreamMetadataAsync(streamId, userCredentials: userCredentials) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task WriteMeta( - string streamId, UserCredentials? userCredentials = default, - string? role = default - ) => - Client.SetStreamMetadataAsync( + public Task WriteMeta(string streamId, UserCredentials? userCredentials = default, string? role = default) => + Streams.SetStreamMetadataAsync( streamId, StreamState.Any, new( @@ -246,7 +244,7 @@ public Task WriteMeta( public async Task SubscribeToStream(string streamId, UserCredentials? userCredentials = default) { var source = new TaskCompletionSource(); - using (await Client.SubscribeToStreamAsync( + using (await Streams.SubscribeToStreamAsync( streamId, FromStream.Start, (_, _, _) => { @@ -267,7 +265,7 @@ public async Task SubscribeToStream(string streamId, UserCredentials? userCreden public async Task SubscribeToAll(UserCredentials? userCredentials = default) { var source = new TaskCompletionSource(); - using (await Client.SubscribeToAllAsync( + using (await Streams.SubscribeToAllAsync( FromAll.Start, (_, _, _) => { source.TrySetResult(true); @@ -286,12 +284,8 @@ public async Task SubscribeToAll(UserCredentials? userCredentials = default) { } } - public async Task CreateStreamWithMeta( - StreamMetadata metadata, - [CallerMemberName] - string streamId = "" - ) { - await Client.SetStreamMetadataAsync( + public async Task CreateStreamWithMeta(StreamMetadata metadata, [CallerMemberName] string streamId = "") { + await Streams.SetStreamMetadataAsync( streamId, StreamState.NoStream, metadata, @@ -303,11 +297,6 @@ await Client.SetStreamMetadataAsync( } public Task DeleteStream(string streamId, UserCredentials? userCredentials = default) => - Client.TombstoneAsync(streamId, StreamState.Any, userCredentials: userCredentials) + Streams.TombstoneAsync(streamId, StreamState.Any, userCredentials: userCredentials) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - - public override async Task DisposeAsync() { - await UserManagementClient.DisposeAsync(); - await base.DisposeAsync(); - } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/all_stream_with_no_acl_security.cs b/test/EventStore.Client.Streams.Tests/Security/all_stream_with_no_acl_security.cs index e9d6013f9..215e0f05d 100644 --- a/test/EventStore.Client.Streams.Tests/Security/all_stream_with_no_acl_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/all_stream_with_no_acl_security.cs @@ -1,71 +1,62 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class all_stream_with_no_acl_security - : IClassFixture { - readonly Fixture _fixture; - - public all_stream_with_no_acl_security(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class all_stream_with_no_acl_security(ITestOutputHelper output, all_stream_with_no_acl_security.CustomFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task write_to_all_is_never_allowed() { - await Assert.ThrowsAsync(() => _fixture.AppendStream(SecurityFixture.AllStream)); - await Assert.ThrowsAsync(() => _fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => _fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestAdmin)); + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.AllStream)); + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestAdmin)); } [Fact] public async Task delete_of_all_is_never_allowed() { - await Assert.ThrowsAsync(() => _fixture.DeleteStream(SecurityFixture.AllStream)); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestAdmin)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(SecurityFixture.AllStream)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestAdmin)); } [Fact] public async Task reading_and_subscribing_is_not_allowed_when_no_credentials_are_passed() { - await Assert.ThrowsAsync(() => _fixture.ReadAllForward()); - await Assert.ThrowsAsync(() => _fixture.ReadAllBackward()); - await Assert.ThrowsAsync(() => _fixture.ReadMeta(SecurityFixture.AllStream)); - await Assert.ThrowsAsync(() => _fixture.SubscribeToAll()); + await Assert.ThrowsAsync(() => Fixture.ReadAllForward()); + await Assert.ThrowsAsync(() => Fixture.ReadAllBackward()); + await Assert.ThrowsAsync(() => Fixture.ReadMeta(SecurityFixture.AllStream)); + await Assert.ThrowsAsync(() => Fixture.SubscribeToAll()); } [Fact] public async Task reading_and_subscribing_is_not_allowed_for_usual_user() { - await Assert.ThrowsAsync(() => _fixture.ReadAllForward(TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => _fixture.ReadAllBackward(TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => _fixture.ReadMeta(SecurityFixture.AllStream, TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => _fixture.SubscribeToAll(TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.ReadAllForward(TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.ReadAllBackward(TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.ReadMeta(SecurityFixture.AllStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.SubscribeToAll(TestCredentials.TestUser1)); } [Fact] public async Task reading_and_subscribing_is_allowed_for_admin_user() { - await _fixture.ReadAllForward(TestCredentials.TestAdmin); - await _fixture.ReadAllBackward(TestCredentials.TestAdmin); - await _fixture.ReadMeta(SecurityFixture.AllStream, TestCredentials.TestAdmin); - await _fixture.SubscribeToAll(TestCredentials.TestAdmin); + await Fixture.ReadAllForward(TestCredentials.TestAdmin); + await Fixture.ReadAllBackward(TestCredentials.TestAdmin); + await Fixture.ReadMeta(SecurityFixture.AllStream, TestCredentials.TestAdmin); + await Fixture.SubscribeToAll(TestCredentials.TestAdmin); } [Fact] public async Task meta_write_is_not_allowed_when_no_credentials_are_passed() => - await Assert.ThrowsAsync(() => _fixture.WriteMeta(SecurityFixture.AllStream)); + await Assert.ThrowsAsync(() => Fixture.WriteMeta(SecurityFixture.AllStream)); [Fact] public async Task meta_write_is_not_allowed_for_usual_user() => - await Assert.ThrowsAsync(() => _fixture.WriteMeta(SecurityFixture.AllStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.WriteMeta(SecurityFixture.AllStream, TestCredentials.TestUser1)); [Fact] - public Task meta_write_is_allowed_for_admin_user() => _fixture.WriteMeta(SecurityFixture.AllStream, TestCredentials.TestAdmin); + public Task meta_write_is_allowed_for_admin_user() => + Fixture.WriteMeta(SecurityFixture.AllStream, TestCredentials.TestAdmin); - public class Fixture : SecurityFixture { + public class CustomFixture : SecurityFixture { protected override async Task Given() { await base.Given(); - await Client.SetStreamMetadataAsync( - AllStream, - StreamState.Any, - new(), - userCredentials: TestCredentials.Root - ); - } - protected override Task When() => Task.CompletedTask; + await Streams.SetStreamMetadataAsync(AllStream, StreamState.Any, new(), userCredentials: TestCredentials.Root); + } } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/delete_stream_security.cs b/test/EventStore.Client.Streams.Tests/Security/delete_stream_security.cs index 3b9132250..61e6253ce 100644 --- a/test/EventStore.Client.Streams.Tests/Security/delete_stream_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/delete_stream_security.cs @@ -1,248 +1,241 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class delete_stream_security : IClassFixture { - readonly Fixture _fixture; - - public delete_stream_security(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class delete_stream_security(ITestOutputHelper output, SecurityFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task delete_of_all_is_never_allowed() { - await Assert.ThrowsAsync(() => _fixture.DeleteStream(SecurityFixture.AllStream)); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestAdmin)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(SecurityFixture.AllStream)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestAdmin)); } [AnonymousAccess.Fact] public async Task deleting_normal_no_acl_stream_with_no_user_is_allowed() { - var streamId = await _fixture.CreateStreamWithMeta(new()); - await _fixture.DeleteStream(streamId); + var streamId = await Fixture.CreateStreamWithMeta(new()); + await Fixture.DeleteStream(streamId); } [Fact] public async Task deleting_normal_no_acl_stream_with_existing_user_is_allowed() { - var streamId = await _fixture.CreateStreamWithMeta(new()); - await _fixture.DeleteStream(streamId, TestCredentials.TestUser1); + var streamId = await Fixture.CreateStreamWithMeta(new()); + await Fixture.DeleteStream(streamId, TestCredentials.TestUser1); } [Fact] public async Task deleting_normal_no_acl_stream_with_admin_user_is_allowed() { - var streamId = await _fixture.CreateStreamWithMeta(new()); - await _fixture.DeleteStream(streamId, TestCredentials.TestAdmin); + var streamId = await Fixture.CreateStreamWithMeta(new()); + await Fixture.DeleteStream(streamId, TestCredentials.TestAdmin); } [Fact] public async Task deleting_normal_user_stream_with_no_user_is_not_allowed() { var streamId = - await _fixture.CreateStreamWithMeta(new(acl: new(deleteRole: TestCredentials.TestUser1.Username))); + await Fixture.CreateStreamWithMeta(new(acl: new(deleteRole: TestCredentials.TestUser1.Username))); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(streamId)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(streamId)); } [Fact] public async Task deleting_normal_user_stream_with_not_authorized_user_is_not_allowed() { var streamId = - await _fixture.CreateStreamWithMeta(new(acl: new(deleteRole: TestCredentials.TestUser1.Username))); + await Fixture.CreateStreamWithMeta(new(acl: new(deleteRole: TestCredentials.TestUser1.Username))); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(streamId, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(streamId, TestCredentials.TestUser2)); } [Fact] public async Task deleting_normal_user_stream_with_authorized_user_is_allowed() { var streamId = - await _fixture.CreateStreamWithMeta(new(acl: new(deleteRole: TestCredentials.TestUser1.Username))); + await Fixture.CreateStreamWithMeta(new(acl: new(deleteRole: TestCredentials.TestUser1.Username))); - await _fixture.DeleteStream(streamId, TestCredentials.TestUser1); + await Fixture.DeleteStream(streamId, TestCredentials.TestUser1); } [Fact] public async Task deleting_normal_user_stream_with_admin_user_is_allowed() { var streamId = - await _fixture.CreateStreamWithMeta(new(acl: new(deleteRole: TestCredentials.TestUser1.Username))); + await Fixture.CreateStreamWithMeta(new(acl: new(deleteRole: TestCredentials.TestUser1.Username))); - await _fixture.DeleteStream(streamId, TestCredentials.TestAdmin); + await Fixture.DeleteStream(streamId, TestCredentials.TestAdmin); } [Fact] public async Task deleting_normal_admin_stream_with_no_user_is_not_allowed() { var streamId = - await _fixture.CreateStreamWithMeta(new(acl: new(deleteRole: SystemRoles.Admins))); + await Fixture.CreateStreamWithMeta(new(acl: new(deleteRole: SystemRoles.Admins))); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(streamId)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(streamId)); } [Fact] public async Task deleting_normal_admin_stream_with_existing_user_is_not_allowed() { var streamId = - await _fixture.CreateStreamWithMeta(new(acl: new(deleteRole: SystemRoles.Admins))); + await Fixture.CreateStreamWithMeta(new(acl: new(deleteRole: SystemRoles.Admins))); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(streamId, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(streamId, TestCredentials.TestUser1)); } [Fact] public async Task deleting_normal_admin_stream_with_admin_user_is_allowed() { var streamId = - await _fixture.CreateStreamWithMeta(new(acl: new(deleteRole: SystemRoles.Admins))); + await Fixture.CreateStreamWithMeta(new(acl: new(deleteRole: SystemRoles.Admins))); - await _fixture.DeleteStream(streamId, TestCredentials.TestAdmin); + await Fixture.DeleteStream(streamId, TestCredentials.TestAdmin); } [AnonymousAccess.Fact] public async Task deleting_normal_all_stream_with_no_user_is_allowed() { var streamId = - await _fixture.CreateStreamWithMeta(new(acl: new(deleteRole: SystemRoles.All))); + await Fixture.CreateStreamWithMeta(new(acl: new(deleteRole: SystemRoles.All))); - await _fixture.DeleteStream(streamId); + await Fixture.DeleteStream(streamId); } [Fact] public async Task deleting_normal_all_stream_with_existing_user_is_allowed() { var streamId = - await _fixture.CreateStreamWithMeta(new(acl: new(deleteRole: SystemRoles.All))); + await Fixture.CreateStreamWithMeta(new(acl: new(deleteRole: SystemRoles.All))); - await _fixture.DeleteStream(streamId, TestCredentials.TestUser1); + await Fixture.DeleteStream(streamId, TestCredentials.TestUser1); } [Fact] public async Task deleting_normal_all_stream_with_admin_user_is_allowed() { var streamId = - await _fixture.CreateStreamWithMeta(new(acl: new(deleteRole: SystemRoles.All))); + await Fixture.CreateStreamWithMeta(new(acl: new(deleteRole: SystemRoles.All))); - await _fixture.DeleteStream(streamId, TestCredentials.TestAdmin); + await Fixture.DeleteStream(streamId, TestCredentials.TestAdmin); } // $-stream [Fact] public async Task deleting_system_no_acl_stream_with_no_user_is_not_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new() ); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(streamId)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(streamId)); } [Fact] public async Task deleting_system_no_acl_stream_with_existing_user_is_not_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new() ); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(streamId, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(streamId, TestCredentials.TestUser1)); } [Fact] public async Task deleting_system_no_acl_stream_with_admin_user_is_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new() ); - await _fixture.DeleteStream(streamId, TestCredentials.TestAdmin); + await Fixture.DeleteStream(streamId, TestCredentials.TestAdmin); } [Fact] public async Task deleting_system_user_stream_with_no_user_is_not_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new(acl: new(deleteRole: TestCredentials.TestUser1.Username)) ); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(streamId)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(streamId)); } [Fact] public async Task deleting_system_user_stream_with_not_authorized_user_is_not_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new(acl: new(deleteRole: TestCredentials.TestUser1.Username)) ); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(streamId, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(streamId, TestCredentials.TestUser2)); } [Fact] public async Task deleting_system_user_stream_with_authorized_user_is_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new(acl: new(deleteRole: TestCredentials.TestUser1.Username)) ); - await _fixture.DeleteStream(streamId, TestCredentials.TestUser1); + await Fixture.DeleteStream(streamId, TestCredentials.TestUser1); } [Fact] public async Task deleting_system_user_stream_with_admin_user_is_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new(acl: new(deleteRole: TestCredentials.TestUser1.Username)) ); - await _fixture.DeleteStream(streamId, TestCredentials.TestAdmin); + await Fixture.DeleteStream(streamId, TestCredentials.TestAdmin); } [Fact] public async Task deleting_system_admin_stream_with_no_user_is_not_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new(acl: new(deleteRole: SystemRoles.Admins)) ); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(streamId)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(streamId)); } [Fact] public async Task deleting_system_admin_stream_with_existing_user_is_not_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new(acl: new(deleteRole: SystemRoles.Admins)) ); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(streamId, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(streamId, TestCredentials.TestUser1)); } [Fact] public async Task deleting_system_admin_stream_with_admin_user_is_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new(acl: new(deleteRole: SystemRoles.Admins)) ); - await _fixture.DeleteStream(streamId, TestCredentials.TestAdmin); + await Fixture.DeleteStream(streamId, TestCredentials.TestAdmin); } [AnonymousAccess.Fact] public async Task deleting_system_all_stream_with_no_user_is_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new(acl: new(deleteRole: SystemRoles.All)) ); - await _fixture.DeleteStream(streamId); + await Fixture.DeleteStream(streamId); } [Fact] public async Task deleting_system_all_stream_with_existing_user_is_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new(acl: new(deleteRole: SystemRoles.All)) ); - await _fixture.DeleteStream(streamId, TestCredentials.TestUser1); + await Fixture.DeleteStream(streamId, TestCredentials.TestUser1); } [Fact] public async Task deleting_system_all_stream_with_admin_user_is_allowed() { - var streamId = await _fixture.CreateStreamWithMeta( - streamId: $"${_fixture.GetStreamName()}", + var streamId = await Fixture.CreateStreamWithMeta( + streamId: $"${Fixture.GetStreamName()}", metadata: new(acl: new(deleteRole: SystemRoles.All)) ); - await _fixture.DeleteStream(streamId, TestCredentials.TestAdmin); - } - - public class Fixture : SecurityFixture { - protected override Task When() => Task.CompletedTask; + await Fixture.DeleteStream(streamId, TestCredentials.TestAdmin); } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/multiple_role_security.cs b/test/EventStore.Client.Streams.Tests/Security/multiple_role_security.cs index 209e48b22..3195c22df 100644 --- a/test/EventStore.Client.Streams.Tests/Security/multiple_role_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/multiple_role_security.cs @@ -1,32 +1,30 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class multiple_role_security : IClassFixture { - readonly Fixture _fixture; - - public multiple_role_security(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class multiple_role_security(ITestOutputHelper output, multiple_role_security.CustomFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task multiple_roles_are_handled_correctly() { - await Assert.ThrowsAsync(() => _fixture.ReadEvent("usr-stream")); - await Assert.ThrowsAsync(() => _fixture.ReadEvent("usr-stream", TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => _fixture.ReadEvent("usr-stream", TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.ReadEvent("usr-stream", TestCredentials.TestAdmin)); - - await Assert.ThrowsAsync(() => _fixture.AppendStream("usr-stream")); - await _fixture.AppendStream("usr-stream", TestCredentials.TestUser1); - await Assert.ThrowsAsync(() => _fixture.AppendStream("usr-stream", TestCredentials.TestUser2)); - await _fixture.AppendStream("usr-stream", TestCredentials.TestAdmin); - - await _fixture.DeleteStream("usr-stream2", TestCredentials.TestUser1); - await _fixture.DeleteStream("usr-stream3", TestCredentials.TestUser2); - await _fixture.DeleteStream("usr-stream4", TestCredentials.TestAdmin); + await Assert.ThrowsAsync(() => Fixture.ReadEvent("usr-stream")); + await Assert.ThrowsAsync(() => Fixture.ReadEvent("usr-stream", TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.ReadEvent("usr-stream", TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.ReadEvent("usr-stream", TestCredentials.TestAdmin)); + + await Assert.ThrowsAsync(() => Fixture.AppendStream("usr-stream")); + await Fixture.AppendStream("usr-stream", TestCredentials.TestUser1); + await Assert.ThrowsAsync(() => Fixture.AppendStream("usr-stream", TestCredentials.TestUser2)); + await Fixture.AppendStream("usr-stream", TestCredentials.TestAdmin); + + await Fixture.DeleteStream("usr-stream2", TestCredentials.TestUser1); + await Fixture.DeleteStream("usr-stream3", TestCredentials.TestUser2); + await Fixture.DeleteStream("usr-stream4", TestCredentials.TestAdmin); } [AnonymousAccess.Fact] - public async Task multiple_roles_are_handled_correctly_without_authentication() => await _fixture.DeleteStream("usr-stream1"); + public async Task multiple_roles_are_handled_correctly_without_authentication() => + await Fixture.DeleteStream("usr-stream1"); - public class Fixture : SecurityFixture { - protected override Task When() { + public class CustomFixture : SecurityFixture { + protected override async Task When() { var settings = new SystemSettings( new( new[] { "user1", "user2" }, @@ -35,7 +33,7 @@ protected override Task When() { ) ); - return Client.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + await Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); } } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/overriden_system_stream_security.cs b/test/EventStore.Client.Streams.Tests/Security/overriden_system_stream_security.cs index 87af193ab..0d41c4450 100644 --- a/test/EventStore.Client.Streams.Tests/Security/overriden_system_stream_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/overriden_system_stream_security.cs @@ -1,89 +1,86 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class overriden_system_stream_security : IClassFixture { - readonly Fixture _fixture; - - public overriden_system_stream_security(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class overriden_system_stream_security(ITestOutputHelper output, overriden_system_stream_security.CustomFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task operations_on_system_stream_succeed_for_authorized_user() { - var stream = $"${_fixture.GetStreamName()}"; - await _fixture.AppendStream(stream, TestCredentials.TestUser1); + var stream = $"${Fixture.GetStreamName()}"; + await Fixture.AppendStream(stream, TestCredentials.TestUser1); - await _fixture.ReadEvent(stream, TestCredentials.TestUser1); - await _fixture.ReadStreamForward(stream, TestCredentials.TestUser1); - await _fixture.ReadStreamBackward(stream, TestCredentials.TestUser1); + await Fixture.ReadEvent(stream, TestCredentials.TestUser1); + await Fixture.ReadStreamForward(stream, TestCredentials.TestUser1); + await Fixture.ReadStreamBackward(stream, TestCredentials.TestUser1); - await _fixture.ReadMeta(stream, TestCredentials.TestUser1); - await _fixture.WriteMeta(stream, TestCredentials.TestUser1); + await Fixture.ReadMeta(stream, TestCredentials.TestUser1); + await Fixture.WriteMeta(stream, TestCredentials.TestUser1); - await _fixture.SubscribeToStream(stream, TestCredentials.TestUser1); + await Fixture.SubscribeToStream(stream, TestCredentials.TestUser1); - await _fixture.DeleteStream(stream, TestCredentials.TestUser1); + await Fixture.DeleteStream(stream, TestCredentials.TestUser1); } [Fact] public async Task operations_on_system_stream_fail_for_not_authorized_user() { - var stream = $"${_fixture.GetStreamName()}"; - await Assert.ThrowsAsync(() => _fixture.ReadEvent(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.ReadStreamForward(stream, TestCredentials.TestUser2)); + var stream = $"${Fixture.GetStreamName()}"; + await Assert.ThrowsAsync(() => Fixture.ReadEvent(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(stream, TestCredentials.TestUser2)); await Assert.ThrowsAsync( () => - _fixture.ReadStreamBackward(stream, TestCredentials.TestUser2) + Fixture.ReadStreamBackward(stream, TestCredentials.TestUser2) ); - await Assert.ThrowsAsync(() => _fixture.AppendStream(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.AppendStream(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.ReadMeta(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.WriteMeta(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.ReadMeta(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.WriteMeta(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.SubscribeToStream(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(stream, TestCredentials.TestUser2)); } [Fact] public async Task operations_on_system_stream_fail_for_anonymous_user() { - var stream = $"${_fixture.GetStreamName()}"; - await Assert.ThrowsAsync(() => _fixture.ReadEvent(stream)); - await Assert.ThrowsAsync(() => _fixture.ReadStreamForward(stream)); - await Assert.ThrowsAsync(() => _fixture.ReadStreamBackward(stream)); + var stream = $"${Fixture.GetStreamName()}"; + await Assert.ThrowsAsync(() => Fixture.ReadEvent(stream)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(stream)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamBackward(stream)); - await Assert.ThrowsAsync(() => _fixture.AppendStream(stream)); + await Assert.ThrowsAsync(() => Fixture.AppendStream(stream)); - await Assert.ThrowsAsync(() => _fixture.ReadMeta(stream)); - await Assert.ThrowsAsync(() => _fixture.WriteMeta(stream)); + await Assert.ThrowsAsync(() => Fixture.ReadMeta(stream)); + await Assert.ThrowsAsync(() => Fixture.WriteMeta(stream)); - await Assert.ThrowsAsync(() => _fixture.SubscribeToStream(stream)); + await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(stream)); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(stream)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(stream)); } [Fact] public async Task operations_on_system_stream_succeed_for_admin() { - var stream = $"${_fixture.GetStreamName()}"; - await _fixture.AppendStream(stream, TestCredentials.TestAdmin); + var stream = $"${Fixture.GetStreamName()}"; + await Fixture.AppendStream(stream, TestCredentials.TestAdmin); - await _fixture.ReadEvent(stream, TestCredentials.TestAdmin); - await _fixture.ReadStreamForward(stream, TestCredentials.TestAdmin); - await _fixture.ReadStreamBackward(stream, TestCredentials.TestAdmin); + await Fixture.ReadEvent(stream, TestCredentials.TestAdmin); + await Fixture.ReadStreamForward(stream, TestCredentials.TestAdmin); + await Fixture.ReadStreamBackward(stream, TestCredentials.TestAdmin); - await _fixture.ReadMeta(stream, TestCredentials.TestAdmin); - await _fixture.WriteMeta(stream, TestCredentials.TestAdmin); + await Fixture.ReadMeta(stream, TestCredentials.TestAdmin); + await Fixture.WriteMeta(stream, TestCredentials.TestAdmin); - await _fixture.SubscribeToStream(stream, TestCredentials.TestAdmin); + await Fixture.SubscribeToStream(stream, TestCredentials.TestAdmin); - await _fixture.DeleteStream(stream, TestCredentials.TestAdmin); + await Fixture.DeleteStream(stream, TestCredentials.TestAdmin); } - public class Fixture : SecurityFixture { + public class CustomFixture : SecurityFixture { protected override Task When() { var settings = new SystemSettings( systemStreamAcl: new("user1", "user1", "user1", "user1", "user1"), userStreamAcl: default ); - return Client.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + return Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); } } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/overriden_system_stream_security_for_all.cs b/test/EventStore.Client.Streams.Tests/Security/overriden_system_stream_security_for_all.cs index cbb11e0d6..8324451ef 100644 --- a/test/EventStore.Client.Streams.Tests/Security/overriden_system_stream_security_for_all.cs +++ b/test/EventStore.Client.Streams.Tests/Security/overriden_system_stream_security_for_all.cs @@ -1,61 +1,57 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class overriden_system_stream_security_for_all - : IClassFixture { - readonly Fixture _fixture; - - public overriden_system_stream_security_for_all(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class overriden_system_stream_security_for_all(ITestOutputHelper output, overriden_system_stream_security_for_all.CustomFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task operations_on_system_stream_succeeds_for_user() { - var stream = $"${_fixture.GetStreamName()}"; - await _fixture.AppendStream(stream, TestCredentials.TestUser1); - await _fixture.ReadEvent(stream, TestCredentials.TestUser1); - await _fixture.ReadStreamForward(stream, TestCredentials.TestUser1); - await _fixture.ReadStreamBackward(stream, TestCredentials.TestUser1); + var stream = $"${Fixture.GetStreamName()}"; + await Fixture.AppendStream(stream, TestCredentials.TestUser1); + await Fixture.ReadEvent(stream, TestCredentials.TestUser1); + await Fixture.ReadStreamForward(stream, TestCredentials.TestUser1); + await Fixture.ReadStreamBackward(stream, TestCredentials.TestUser1); - await _fixture.ReadMeta(stream, TestCredentials.TestUser1); - await _fixture.WriteMeta(stream, TestCredentials.TestUser1); + await Fixture.ReadMeta(stream, TestCredentials.TestUser1); + await Fixture.WriteMeta(stream, TestCredentials.TestUser1); - await _fixture.SubscribeToStream(stream, TestCredentials.TestUser1); + await Fixture.SubscribeToStream(stream, TestCredentials.TestUser1); - await _fixture.DeleteStream(stream, TestCredentials.TestUser1); + await Fixture.DeleteStream(stream, TestCredentials.TestUser1); } [AnonymousAccess.Fact] public async Task operations_on_system_stream_fail_for_anonymous_user() { - var stream = $"${_fixture.GetStreamName()}"; - await _fixture.AppendStream(stream); - await _fixture.ReadEvent(stream); - await _fixture.ReadStreamForward(stream); - await _fixture.ReadStreamBackward(stream); + var stream = $"${Fixture.GetStreamName()}"; + await Fixture.AppendStream(stream); + await Fixture.ReadEvent(stream); + await Fixture.ReadStreamForward(stream); + await Fixture.ReadStreamBackward(stream); - await _fixture.ReadMeta(stream); - await _fixture.WriteMeta(stream); + await Fixture.ReadMeta(stream); + await Fixture.WriteMeta(stream); - await _fixture.SubscribeToStream(stream); + await Fixture.SubscribeToStream(stream); - await _fixture.DeleteStream(stream); + await Fixture.DeleteStream(stream); } [Fact] public async Task operations_on_system_stream_succeed_for_admin() { - var stream = $"${_fixture.GetStreamName()}"; - await _fixture.AppendStream(stream, TestCredentials.TestAdmin); + var stream = $"${Fixture.GetStreamName()}"; + await Fixture.AppendStream(stream, TestCredentials.TestAdmin); - await _fixture.ReadEvent(stream, TestCredentials.TestAdmin); - await _fixture.ReadStreamForward(stream, TestCredentials.TestAdmin); - await _fixture.ReadStreamBackward(stream, TestCredentials.TestAdmin); + await Fixture.ReadEvent(stream, TestCredentials.TestAdmin); + await Fixture.ReadStreamForward(stream, TestCredentials.TestAdmin); + await Fixture.ReadStreamBackward(stream, TestCredentials.TestAdmin); - await _fixture.ReadMeta(stream, TestCredentials.TestAdmin); - await _fixture.WriteMeta(stream, TestCredentials.TestAdmin); + await Fixture.ReadMeta(stream, TestCredentials.TestAdmin); + await Fixture.WriteMeta(stream, TestCredentials.TestAdmin); - await _fixture.SubscribeToStream(stream, TestCredentials.TestAdmin); + await Fixture.SubscribeToStream(stream, TestCredentials.TestAdmin); - await _fixture.DeleteStream(stream, TestCredentials.TestAdmin); + await Fixture.DeleteStream(stream, TestCredentials.TestAdmin); } - public class Fixture : SecurityFixture { + public class CustomFixture : SecurityFixture { protected override Task When() { var settings = new SystemSettings( systemStreamAcl: new( @@ -67,7 +63,7 @@ protected override Task When() { ) ); - return Client.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + return Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); } } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/overriden_user_stream_security.cs b/test/EventStore.Client.Streams.Tests/Security/overriden_user_stream_security.cs index b969f2115..ce1ebe1e9 100644 --- a/test/EventStore.Client.Streams.Tests/Security/overriden_user_stream_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/overriden_user_stream_security.cs @@ -1,80 +1,77 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class overriden_user_stream_security : IClassFixture { - readonly Fixture _fixture; - - public overriden_user_stream_security(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class overriden_user_stream_security(ITestOutputHelper output, overriden_user_stream_security.CustomFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task operations_on_user_stream_succeeds_for_authorized_user() { - var stream = _fixture.GetStreamName(); - await _fixture.AppendStream(stream, TestCredentials.TestUser1); + var stream = Fixture.GetStreamName(); + await Fixture.AppendStream(stream, TestCredentials.TestUser1); - await _fixture.ReadEvent(stream, TestCredentials.TestUser1); - await _fixture.ReadStreamForward(stream, TestCredentials.TestUser1); - await _fixture.ReadStreamBackward(stream, TestCredentials.TestUser1); + await Fixture.ReadEvent(stream, TestCredentials.TestUser1); + await Fixture.ReadStreamForward(stream, TestCredentials.TestUser1); + await Fixture.ReadStreamBackward(stream, TestCredentials.TestUser1); - await _fixture.ReadMeta(stream, TestCredentials.TestUser1); - await _fixture.WriteMeta(stream, TestCredentials.TestUser1); + await Fixture.ReadMeta(stream, TestCredentials.TestUser1); + await Fixture.WriteMeta(stream, TestCredentials.TestUser1); - await _fixture.SubscribeToStream(stream, TestCredentials.TestUser1); + await Fixture.SubscribeToStream(stream, TestCredentials.TestUser1); - await _fixture.DeleteStream(stream, TestCredentials.TestUser1); + await Fixture.DeleteStream(stream, TestCredentials.TestUser1); } [Fact] public async Task operations_on_user_stream_fail_for_not_authorized_user() { - var stream = _fixture.GetStreamName(); - await Assert.ThrowsAsync(() => _fixture.ReadEvent(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.ReadStreamForward(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.ReadStreamBackward(stream, TestCredentials.TestUser2)); + var stream = Fixture.GetStreamName(); + await Assert.ThrowsAsync(() => Fixture.ReadEvent(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamBackward(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.AppendStream(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.ReadMeta(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.WriteMeta(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.AppendStream(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.ReadMeta(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.WriteMeta(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.SubscribeToStream(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(stream, TestCredentials.TestUser2)); } [Fact] public async Task operations_on_user_stream_fail_for_anonymous_user() { - var stream = _fixture.GetStreamName(); - await Assert.ThrowsAsync(() => _fixture.ReadEvent(stream)); - await Assert.ThrowsAsync(() => _fixture.ReadStreamForward(stream)); - await Assert.ThrowsAsync(() => _fixture.ReadStreamBackward(stream)); + var stream = Fixture.GetStreamName(); + await Assert.ThrowsAsync(() => Fixture.ReadEvent(stream)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(stream)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamBackward(stream)); - await Assert.ThrowsAsync(() => _fixture.AppendStream(stream)); - await Assert.ThrowsAsync(() => _fixture.ReadMeta(stream)); - await Assert.ThrowsAsync(() => _fixture.WriteMeta(stream)); + await Assert.ThrowsAsync(() => Fixture.AppendStream(stream)); + await Assert.ThrowsAsync(() => Fixture.ReadMeta(stream)); + await Assert.ThrowsAsync(() => Fixture.WriteMeta(stream)); - await Assert.ThrowsAsync(() => _fixture.SubscribeToStream(stream)); + await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(stream)); - await Assert.ThrowsAsync(() => _fixture.DeleteStream(stream)); + await Assert.ThrowsAsync(() => Fixture.DeleteStream(stream)); } [Fact] public async Task operations_on_user_stream_succeed_for_admin() { - var stream = _fixture.GetStreamName(); - await _fixture.AppendStream(stream, TestCredentials.TestAdmin); + var stream = Fixture.GetStreamName(); + await Fixture.AppendStream(stream, TestCredentials.TestAdmin); - await _fixture.ReadEvent(stream, TestCredentials.TestAdmin); - await _fixture.ReadStreamForward(stream, TestCredentials.TestAdmin); - await _fixture.ReadStreamBackward(stream, TestCredentials.TestAdmin); + await Fixture.ReadEvent(stream, TestCredentials.TestAdmin); + await Fixture.ReadStreamForward(stream, TestCredentials.TestAdmin); + await Fixture.ReadStreamBackward(stream, TestCredentials.TestAdmin); - await _fixture.ReadMeta(stream, TestCredentials.TestAdmin); - await _fixture.WriteMeta(stream, TestCredentials.TestAdmin); + await Fixture.ReadMeta(stream, TestCredentials.TestAdmin); + await Fixture.WriteMeta(stream, TestCredentials.TestAdmin); - await _fixture.SubscribeToStream(stream, TestCredentials.TestAdmin); + await Fixture.SubscribeToStream(stream, TestCredentials.TestAdmin); - await _fixture.DeleteStream(stream, TestCredentials.TestAdmin); + await Fixture.DeleteStream(stream, TestCredentials.TestAdmin); } - public class Fixture : SecurityFixture { + public class CustomFixture : SecurityFixture { protected override Task When() { var settings = new SystemSettings(new("user1", "user1", "user1", "user1", "user1")); - return Client.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + return Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); } } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/read_all_security.cs b/test/EventStore.Client.Streams.Tests/Security/read_all_security.cs index 6b6e5539f..01089db2e 100644 --- a/test/EventStore.Client.Streams.Tests/Security/read_all_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/read_all_security.cs @@ -1,41 +1,34 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class read_all_security : IClassFixture { - readonly Fixture _fixture; - - public read_all_security(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class read_all_security(ITestOutputHelper output, SecurityFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task reading_all_with_not_existing_credentials_is_not_authenticated() { - await Assert.ThrowsAsync(() => _fixture.ReadAllForward(TestCredentials.TestBadUser)); - await Assert.ThrowsAsync(() => _fixture.ReadAllBackward(TestCredentials.TestBadUser)); + await Assert.ThrowsAsync(() => Fixture.ReadAllForward(TestCredentials.TestBadUser)); + await Assert.ThrowsAsync(() => Fixture.ReadAllBackward(TestCredentials.TestBadUser)); } [Fact] public async Task reading_all_with_no_credentials_is_denied() { - await Assert.ThrowsAsync(() => _fixture.ReadAllForward()); - await Assert.ThrowsAsync(() => _fixture.ReadAllBackward()); + await Assert.ThrowsAsync(() => Fixture.ReadAllForward()); + await Assert.ThrowsAsync(() => Fixture.ReadAllBackward()); } [Fact] public async Task reading_all_with_not_authorized_user_credentials_is_denied() { - await Assert.ThrowsAsync(() => _fixture.ReadAllForward(TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.ReadAllBackward(TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.ReadAllForward(TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.ReadAllBackward(TestCredentials.TestUser2)); } [Fact] public async Task reading_all_with_authorized_user_credentials_succeeds() { - await _fixture.ReadAllForward(TestCredentials.TestUser1); - await _fixture.ReadAllBackward(TestCredentials.TestUser1); + await Fixture.ReadAllForward(TestCredentials.TestUser1); + await Fixture.ReadAllBackward(TestCredentials.TestUser1); } [Fact] public async Task reading_all_with_admin_credentials_succeeds() { - await _fixture.ReadAllForward(TestCredentials.TestAdmin); - await _fixture.ReadAllBackward(TestCredentials.TestAdmin); - } - - public class Fixture : SecurityFixture { - protected override Task When() => Task.CompletedTask; + await Fixture.ReadAllForward(TestCredentials.TestAdmin); + await Fixture.ReadAllBackward(TestCredentials.TestAdmin); } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/read_stream_meta_security.cs b/test/EventStore.Client.Streams.Tests/Security/read_stream_meta_security.cs index 0192fe371..b3a7e7afa 100644 --- a/test/EventStore.Client.Streams.Tests/Security/read_stream_meta_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/read_stream_meta_security.cs @@ -1,80 +1,69 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class read_stream_meta_security : IClassFixture { - readonly Fixture _fixture; - - public read_stream_meta_security(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class read_stream_meta_security(ITestOutputHelper output, SecurityFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task reading_stream_meta_with_not_existing_credentials_is_not_authenticated() => await Assert.ThrowsAsync( - () => - _fixture.ReadMeta(SecurityFixture.MetaReadStream, TestCredentials.TestBadUser) + () => Fixture.ReadMeta(SecurityFixture.MetaReadStream, TestCredentials.TestBadUser) ); [Fact] public async Task reading_stream_meta_with_no_credentials_is_denied() => - await Assert.ThrowsAsync(() => _fixture.ReadMeta(SecurityFixture.MetaReadStream)); + await Assert.ThrowsAsync(() => Fixture.ReadMeta(SecurityFixture.MetaReadStream)); [Fact] public async Task reading_stream_meta_with_not_authorized_user_credentials_is_denied() => await Assert.ThrowsAsync( - () => - _fixture.ReadMeta(SecurityFixture.MetaReadStream, TestCredentials.TestUser2) + () => Fixture.ReadMeta(SecurityFixture.MetaReadStream, TestCredentials.TestUser2) ); [Fact] public async Task reading_stream_meta_with_authorized_user_credentials_succeeds() => - await _fixture.ReadMeta(SecurityFixture.MetaReadStream, TestCredentials.TestUser1); + await Fixture.ReadMeta(SecurityFixture.MetaReadStream, TestCredentials.TestUser1); [Fact] public async Task reading_stream_meta_with_admin_user_credentials_succeeds() => - await _fixture.ReadMeta(SecurityFixture.MetaReadStream, TestCredentials.TestAdmin); + await Fixture.ReadMeta(SecurityFixture.MetaReadStream, TestCredentials.TestAdmin); [AnonymousAccess.Fact] - public async Task reading_no_acl_stream_meta_succeeds_when_no_credentials_are_passed() => await _fixture.ReadMeta(SecurityFixture.NoAclStream); + public async Task reading_no_acl_stream_meta_succeeds_when_no_credentials_are_passed() => await Fixture.ReadMeta(SecurityFixture.NoAclStream); [Fact] public async Task reading_no_acl_stream_meta_is_not_authenticated_when_not_existing_credentials_are_passed() => await Assert.ThrowsAsync( - () => - _fixture.ReadMeta(SecurityFixture.NoAclStream, TestCredentials.TestBadUser) + () => Fixture.ReadMeta(SecurityFixture.NoAclStream, TestCredentials.TestBadUser) ); [Fact] public async Task reading_no_acl_stream_meta_succeeds_when_any_existing_user_credentials_are_passed() { - await _fixture.ReadMeta(SecurityFixture.NoAclStream, TestCredentials.TestUser1); - await _fixture.ReadMeta(SecurityFixture.NoAclStream, TestCredentials.TestUser2); + await Fixture.ReadMeta(SecurityFixture.NoAclStream, TestCredentials.TestUser1); + await Fixture.ReadMeta(SecurityFixture.NoAclStream, TestCredentials.TestUser2); } [Fact] public async Task reading_no_acl_stream_meta_succeeds_when_admin_user_credentials_are_passed() => - await _fixture.ReadMeta(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); + await Fixture.ReadMeta(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); [AnonymousAccess.Fact] public async Task reading_all_access_normal_stream_meta_succeeds_when_no_credentials_are_passed() => - await _fixture.ReadMeta(SecurityFixture.NormalAllStream); + await Fixture.ReadMeta(SecurityFixture.NormalAllStream); [Fact] public async Task reading_all_access_normal_stream_meta_is_not_authenticated_when_not_existing_credentials_are_passed() => await Assert.ThrowsAsync( - () => - _fixture.ReadMeta(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser) + () => Fixture.ReadMeta(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser) ); [Fact] public async Task reading_all_access_normal_stream_meta_succeeds_when_any_existing_user_credentials_are_passed() { - await _fixture.ReadMeta(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); - await _fixture.ReadMeta(SecurityFixture.NormalAllStream, TestCredentials.TestUser2); + await Fixture.ReadMeta(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); + await Fixture.ReadMeta(SecurityFixture.NormalAllStream, TestCredentials.TestUser2); } [Fact] public async Task reading_all_access_normal_stream_meta_succeeds_when_admin_user_credentials_are_passed() => - await _fixture.ReadMeta(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); - - public class Fixture : SecurityFixture { - protected override Task When() => Task.CompletedTask; - } + await Fixture.ReadMeta(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/read_stream_security.cs b/test/EventStore.Client.Streams.Tests/Security/read_stream_security.cs index a5d4e6c60..206627128 100644 --- a/test/EventStore.Client.Streams.Tests/Security/read_stream_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/read_stream_security.cs @@ -1,164 +1,113 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class read_stream_security : IClassFixture { - readonly Fixture _fixture; - - public read_stream_security(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class read_stream_security(ITestOutputHelper output, SecurityFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task reading_stream_with_not_existing_credentials_is_not_authenticated() { - await Assert.ThrowsAsync( - () => - _fixture.ReadEvent(SecurityFixture.ReadStream, TestCredentials.TestBadUser) - ); - - await Assert.ThrowsAsync( - () => - _fixture.ReadStreamForward(SecurityFixture.ReadStream, TestCredentials.TestBadUser) - ); - - await Assert.ThrowsAsync( - () => - _fixture.ReadStreamBackward(SecurityFixture.ReadStream, TestCredentials.TestBadUser) - ); + await Assert.ThrowsAsync(() => Fixture.ReadEvent(SecurityFixture.ReadStream, TestCredentials.TestBadUser)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(SecurityFixture.ReadStream, TestCredentials.TestBadUser)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamBackward(SecurityFixture.ReadStream, TestCredentials.TestBadUser)); } [Fact] public async Task reading_stream_with_no_credentials_is_denied() { - await Assert.ThrowsAsync(() => _fixture.ReadEvent(SecurityFixture.ReadStream)); - await Assert.ThrowsAsync(() => _fixture.ReadStreamForward(SecurityFixture.ReadStream)); - await Assert.ThrowsAsync( - () => - _fixture.ReadStreamBackward(SecurityFixture.ReadStream) - ); + await Assert.ThrowsAsync(() => Fixture.ReadEvent(SecurityFixture.ReadStream)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(SecurityFixture.ReadStream)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamBackward(SecurityFixture.ReadStream)); } [Fact] public async Task reading_stream_with_not_authorized_user_credentials_is_denied() { - await Assert.ThrowsAsync( - () => - _fixture.ReadEvent(SecurityFixture.ReadStream, TestCredentials.TestUser2) - ); - - await Assert.ThrowsAsync( - () => - _fixture.ReadStreamForward(SecurityFixture.ReadStream, TestCredentials.TestUser2) - ); - - await Assert.ThrowsAsync( - () => - _fixture.ReadStreamBackward(SecurityFixture.ReadStream, TestCredentials.TestUser2) - ); + await Assert.ThrowsAsync(() => Fixture.ReadEvent(SecurityFixture.ReadStream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(SecurityFixture.ReadStream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamBackward(SecurityFixture.ReadStream, TestCredentials.TestUser2)); } [Fact] public async Task reading_stream_with_authorized_user_credentials_succeeds() { - await _fixture.AppendStream(SecurityFixture.ReadStream, TestCredentials.TestUser1); - - await _fixture.ReadEvent(SecurityFixture.ReadStream, TestCredentials.TestUser1); - await _fixture.ReadStreamForward(SecurityFixture.ReadStream, TestCredentials.TestUser1); - await _fixture.ReadStreamBackward(SecurityFixture.ReadStream, TestCredentials.TestUser1); + await Fixture.AppendStream(SecurityFixture.ReadStream, TestCredentials.TestUser1); + + await Fixture.ReadEvent(SecurityFixture.ReadStream, TestCredentials.TestUser1); + await Fixture.ReadStreamForward(SecurityFixture.ReadStream, TestCredentials.TestUser1); + await Fixture.ReadStreamBackward(SecurityFixture.ReadStream, TestCredentials.TestUser1); } [Fact] public async Task reading_stream_with_admin_user_credentials_succeeds() { - await _fixture.AppendStream(SecurityFixture.ReadStream, TestCredentials.TestAdmin); + await Fixture.AppendStream(SecurityFixture.ReadStream, TestCredentials.TestAdmin); - await _fixture.ReadEvent(SecurityFixture.ReadStream, TestCredentials.TestAdmin); - await _fixture.ReadStreamForward(SecurityFixture.ReadStream, TestCredentials.TestAdmin); - await _fixture.ReadStreamBackward(SecurityFixture.ReadStream, TestCredentials.TestAdmin); + await Fixture.ReadEvent(SecurityFixture.ReadStream, TestCredentials.TestAdmin); + await Fixture.ReadStreamForward(SecurityFixture.ReadStream, TestCredentials.TestAdmin); + await Fixture.ReadStreamBackward(SecurityFixture.ReadStream, TestCredentials.TestAdmin); } [AnonymousAccess.Fact] public async Task reading_no_acl_stream_succeeds_when_no_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NoAclStream); + await Fixture.AppendStream(SecurityFixture.NoAclStream); - await _fixture.ReadEvent(SecurityFixture.NoAclStream); - await _fixture.ReadStreamForward(SecurityFixture.NoAclStream); - await _fixture.ReadStreamBackward(SecurityFixture.NoAclStream); + await Fixture.ReadEvent(SecurityFixture.NoAclStream); + await Fixture.ReadStreamForward(SecurityFixture.NoAclStream); + await Fixture.ReadStreamBackward(SecurityFixture.NoAclStream); } [Fact] public async Task reading_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed() { - await Assert.ThrowsAsync(() => _fixture.ReadEvent(SecurityFixture.NoAclStream, TestCredentials.TestBadUser)); - await Assert.ThrowsAsync( - () => - _fixture.ReadStreamForward(SecurityFixture.NoAclStream, TestCredentials.TestBadUser) - ); - - await Assert.ThrowsAsync( - () => - _fixture.ReadStreamBackward(SecurityFixture.NoAclStream, TestCredentials.TestBadUser) - ); + await Assert.ThrowsAsync(() => Fixture.ReadEvent(SecurityFixture.NoAclStream, TestCredentials.TestBadUser)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(SecurityFixture.NoAclStream, TestCredentials.TestBadUser)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamBackward(SecurityFixture.NoAclStream, TestCredentials.TestBadUser)); } [Fact] public async Task reading_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestUser1); - - await _fixture.ReadEvent(SecurityFixture.NoAclStream, TestCredentials.TestUser1); - await _fixture.ReadStreamForward(SecurityFixture.NoAclStream, TestCredentials.TestUser1); - await _fixture.ReadStreamBackward(SecurityFixture.NoAclStream, TestCredentials.TestUser1); - await _fixture.ReadEvent(SecurityFixture.NoAclStream, TestCredentials.TestUser2); - await _fixture.ReadStreamForward(SecurityFixture.NoAclStream, TestCredentials.TestUser2); - await _fixture.ReadStreamBackward(SecurityFixture.NoAclStream, TestCredentials.TestUser2); + await Fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestUser1); + + await Fixture.ReadEvent(SecurityFixture.NoAclStream, TestCredentials.TestUser1); + await Fixture.ReadStreamForward(SecurityFixture.NoAclStream, TestCredentials.TestUser1); + await Fixture.ReadStreamBackward(SecurityFixture.NoAclStream, TestCredentials.TestUser1); + await Fixture.ReadEvent(SecurityFixture.NoAclStream, TestCredentials.TestUser2); + await Fixture.ReadStreamForward(SecurityFixture.NoAclStream, TestCredentials.TestUser2); + await Fixture.ReadStreamBackward(SecurityFixture.NoAclStream, TestCredentials.TestUser2); } [Fact] public async Task reading_no_acl_stream_succeeds_when_admin_user_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); - await _fixture.ReadEvent(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); - await _fixture.ReadStreamForward(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); - await _fixture.ReadStreamBackward(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); + await Fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); + await Fixture.ReadEvent(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); + await Fixture.ReadStreamForward(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); + await Fixture.ReadStreamBackward(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); } [AnonymousAccess.Fact] public async Task reading_all_access_normal_stream_succeeds_when_no_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NormalAllStream); - await _fixture.ReadEvent(SecurityFixture.NormalAllStream); - await _fixture.ReadStreamForward(SecurityFixture.NormalAllStream); - await _fixture.ReadStreamBackward(SecurityFixture.NormalAllStream); + await Fixture.AppendStream(SecurityFixture.NormalAllStream); + await Fixture.ReadEvent(SecurityFixture.NormalAllStream); + await Fixture.ReadStreamForward(SecurityFixture.NormalAllStream); + await Fixture.ReadStreamBackward(SecurityFixture.NormalAllStream); } [Fact] - public async Task - reading_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() { - await Assert.ThrowsAsync( - () => - _fixture.ReadEvent(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser) - ); - - await Assert.ThrowsAsync( - () => - _fixture.ReadStreamForward(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser) - ); - - await Assert.ThrowsAsync( - () => - _fixture.ReadStreamBackward(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser) - ); + public async Task reading_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() { + await Assert.ThrowsAsync(() => Fixture.ReadEvent(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamBackward(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser)); } [Fact] public async Task reading_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); - await _fixture.ReadEvent(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); - await _fixture.ReadStreamForward(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); - await _fixture.ReadStreamBackward(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); - await _fixture.ReadEvent(SecurityFixture.NormalAllStream, TestCredentials.TestUser2); - await _fixture.ReadStreamForward(SecurityFixture.NormalAllStream, TestCredentials.TestUser2); - await _fixture.ReadStreamBackward(SecurityFixture.NormalAllStream, TestCredentials.TestUser2); + await Fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); + await Fixture.ReadEvent(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); + await Fixture.ReadStreamForward(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); + await Fixture.ReadStreamBackward(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); + await Fixture.ReadEvent(SecurityFixture.NormalAllStream, TestCredentials.TestUser2); + await Fixture.ReadStreamForward(SecurityFixture.NormalAllStream, TestCredentials.TestUser2); + await Fixture.ReadStreamBackward(SecurityFixture.NormalAllStream, TestCredentials.TestUser2); } [Fact] public async Task reading_all_access_normal_stream_succeeds_when_admin_user_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); - await _fixture.ReadEvent(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); - await _fixture.ReadStreamForward(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); - await _fixture.ReadStreamBackward(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); - } - - public class Fixture : SecurityFixture { - protected override Task When() => Task.CompletedTask; + await Fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); + await Fixture.ReadEvent(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); + await Fixture.ReadStreamForward(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); + await Fixture.ReadStreamBackward(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/stream_security_inheritance.cs b/test/EventStore.Client.Streams.Tests/Security/stream_security_inheritance.cs index 32a0798d2..1e1221e16 100644 --- a/test/EventStore.Client.Streams.Tests/Security/stream_security_inheritance.cs +++ b/test/EventStore.Client.Streams.Tests/Security/stream_security_inheritance.cs @@ -1,223 +1,179 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class stream_security_inheritance : IClassFixture { - readonly Fixture _fixture; - - public stream_security_inheritance(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class stream_security_inheritance(ITestOutputHelper output, stream_security_inheritance.CustomFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task acl_inheritance_is_working_properly_on_user_streams() { - await Assert.ThrowsAsync(() => _fixture.AppendStream("user-no-acl")); - await _fixture.AppendStream("user-no-acl", TestCredentials.TestUser1); - await Assert.ThrowsAsync( - () => - _fixture.AppendStream("user-no-acl", TestCredentials.TestUser2) - ); - - await _fixture.AppendStream("user-no-acl", TestCredentials.TestAdmin); - - await Assert.ThrowsAsync(() => _fixture.AppendStream("user-w-diff")); - await Assert.ThrowsAsync( - () => - _fixture.AppendStream("user-w-diff", TestCredentials.TestUser1) - ); - - await _fixture.AppendStream("user-w-diff", TestCredentials.TestUser2); - await _fixture.AppendStream("user-w-diff", TestCredentials.TestAdmin); - - await Assert.ThrowsAsync(() => _fixture.AppendStream("user-w-multiple")); - await _fixture.AppendStream("user-w-multiple", TestCredentials.TestUser1); - await _fixture.AppendStream("user-w-multiple", TestCredentials.TestUser2); - await _fixture.AppendStream("user-w-multiple", TestCredentials.TestAdmin); - - await Assert.ThrowsAsync( - () => - _fixture.AppendStream("user-w-restricted") - ); - - await Assert.ThrowsAsync( - () => - _fixture.AppendStream("user-w-restricted", TestCredentials.TestUser1) - ); - - await Assert.ThrowsAsync( - () => - _fixture.AppendStream("user-w-restricted", TestCredentials.TestUser2) - ); - - await _fixture.AppendStream("user-w-restricted", TestCredentials.TestAdmin); - - await _fixture.AppendStream("user-w-all", TestCredentials.TestUser1); - await _fixture.AppendStream("user-w-all", TestCredentials.TestUser2); - await _fixture.AppendStream("user-w-all", TestCredentials.TestAdmin); - - await _fixture.ReadEvent("user-no-acl", TestCredentials.TestUser1); - await _fixture.ReadEvent("user-no-acl", TestCredentials.TestUser2); - await _fixture.ReadEvent("user-no-acl", TestCredentials.TestAdmin); - - await Assert.ThrowsAsync(() => _fixture.ReadEvent("user-r-restricted")); - await _fixture.AppendStream("user-r-restricted", TestCredentials.TestUser1); - await _fixture.ReadEvent("user-r-restricted", TestCredentials.TestUser1); - await Assert.ThrowsAsync( - () => - _fixture.ReadEvent("user-r-restricted", TestCredentials.TestUser2) - ); - - await _fixture.ReadEvent("user-r-restricted", TestCredentials.TestAdmin); + await Assert.ThrowsAsync(() => Fixture.AppendStream("user-no-acl")); + await Fixture.AppendStream("user-no-acl", TestCredentials.TestUser1); + await Assert.ThrowsAsync(() => Fixture.AppendStream("user-no-acl", TestCredentials.TestUser2)); + + await Fixture.AppendStream("user-no-acl", TestCredentials.TestAdmin); + + await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-diff")); + await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-diff", TestCredentials.TestUser1)); + + await Fixture.AppendStream("user-w-diff", TestCredentials.TestUser2); + await Fixture.AppendStream("user-w-diff", TestCredentials.TestAdmin); + + await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-multiple")); + await Fixture.AppendStream("user-w-multiple", TestCredentials.TestUser1); + await Fixture.AppendStream("user-w-multiple", TestCredentials.TestUser2); + await Fixture.AppendStream("user-w-multiple", TestCredentials.TestAdmin); + + await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-restricted")); + + await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-restricted", TestCredentials.TestUser1)); + + await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-restricted", TestCredentials.TestUser2)); + + await Fixture.AppendStream("user-w-restricted", TestCredentials.TestAdmin); + + await Fixture.AppendStream("user-w-all", TestCredentials.TestUser1); + await Fixture.AppendStream("user-w-all", TestCredentials.TestUser2); + await Fixture.AppendStream("user-w-all", TestCredentials.TestAdmin); + + await Fixture.ReadEvent("user-no-acl", TestCredentials.TestUser1); + await Fixture.ReadEvent("user-no-acl", TestCredentials.TestUser2); + await Fixture.ReadEvent("user-no-acl", TestCredentials.TestAdmin); + + await Assert.ThrowsAsync(() => Fixture.ReadEvent("user-r-restricted")); + await Fixture.AppendStream("user-r-restricted", TestCredentials.TestUser1); + await Fixture.ReadEvent("user-r-restricted", TestCredentials.TestUser1); + await Assert.ThrowsAsync(() => Fixture.ReadEvent("user-r-restricted", TestCredentials.TestUser2)); + + await Fixture.ReadEvent("user-r-restricted", TestCredentials.TestAdmin); } [AnonymousAccess.Fact] public async Task acl_inheritance_is_working_properly_on_user_streams_when_not_authenticated() { - await _fixture.AppendStream("user-w-all"); + await Fixture.AppendStream("user-w-all"); // make sure the stream exists before trying to read it without authentication - await _fixture.AppendStream("user-no-acl", TestCredentials.TestAdmin); - await _fixture.ReadEvent("user-no-acl"); + await Fixture.AppendStream("user-no-acl", TestCredentials.TestAdmin); + await Fixture.ReadEvent("user-no-acl"); } [Fact] public async Task acl_inheritance_is_working_properly_on_system_streams() { - await Assert.ThrowsAsync(() => _fixture.AppendStream("$sys-no-acl")); - await _fixture.AppendStream("$sys-no-acl", TestCredentials.TestUser1); - await Assert.ThrowsAsync( - () => - _fixture.AppendStream("$sys-no-acl", TestCredentials.TestUser2) - ); - - await _fixture.AppendStream("$sys-no-acl", TestCredentials.TestAdmin); - - await Assert.ThrowsAsync(() => _fixture.AppendStream("$sys-w-diff")); - await Assert.ThrowsAsync( - () => - _fixture.AppendStream("$sys-w-diff", TestCredentials.TestUser1) - ); - - await _fixture.AppendStream("$sys-w-diff", TestCredentials.TestUser2); - await _fixture.AppendStream("$sys-w-diff", TestCredentials.TestAdmin); - - await Assert.ThrowsAsync(() => _fixture.AppendStream("$sys-w-multiple")); - await _fixture.AppendStream("$sys-w-multiple", TestCredentials.TestUser1); - await _fixture.AppendStream("$sys-w-multiple", TestCredentials.TestUser2); - await _fixture.AppendStream("$sys-w-multiple", TestCredentials.TestAdmin); - - await Assert.ThrowsAsync( - () => - _fixture.AppendStream("$sys-w-restricted") - ); - - await Assert.ThrowsAsync( - () => - _fixture.AppendStream("$sys-w-restricted", TestCredentials.TestUser1) - ); - - await Assert.ThrowsAsync( - () => - _fixture.AppendStream("$sys-w-restricted", TestCredentials.TestUser2) - ); - - await _fixture.AppendStream("$sys-w-restricted", TestCredentials.TestAdmin); - - await _fixture.AppendStream("$sys-w-all", TestCredentials.TestUser1); - await _fixture.AppendStream("$sys-w-all", TestCredentials.TestUser2); - await _fixture.AppendStream("$sys-w-all", TestCredentials.TestAdmin); - - await Assert.ThrowsAsync(() => _fixture.ReadEvent("$sys-no-acl")); - await Assert.ThrowsAsync( - () => - _fixture.ReadEvent("$sys-no-acl", TestCredentials.TestUser1) - ); - - await Assert.ThrowsAsync( - () => - _fixture.ReadEvent("$sys-no-acl", TestCredentials.TestUser2) - ); - - await _fixture.ReadEvent("$sys-no-acl", TestCredentials.TestAdmin); + await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-no-acl")); + await Fixture.AppendStream("$sys-no-acl", TestCredentials.TestUser1); + await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-no-acl", TestCredentials.TestUser2)); + + await Fixture.AppendStream("$sys-no-acl", TestCredentials.TestAdmin); + + await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-diff")); + await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-diff", TestCredentials.TestUser1)); + + await Fixture.AppendStream("$sys-w-diff", TestCredentials.TestUser2); + await Fixture.AppendStream("$sys-w-diff", TestCredentials.TestAdmin); + + await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-multiple")); + await Fixture.AppendStream("$sys-w-multiple", TestCredentials.TestUser1); + await Fixture.AppendStream("$sys-w-multiple", TestCredentials.TestUser2); + await Fixture.AppendStream("$sys-w-multiple", TestCredentials.TestAdmin); + + await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-restricted")); + await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestUser2)); + + await Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestAdmin); + + await Fixture.AppendStream("$sys-w-all", TestCredentials.TestUser1); + await Fixture.AppendStream("$sys-w-all", TestCredentials.TestUser2); + await Fixture.AppendStream("$sys-w-all", TestCredentials.TestAdmin); + + await Assert.ThrowsAsync(() => Fixture.ReadEvent("$sys-no-acl")); + await Assert.ThrowsAsync(() => Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestUser1)); + + await Assert.ThrowsAsync(() => Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestUser2)); + + await Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestAdmin); } [AnonymousAccess.Fact] - public async Task acl_inheritance_is_working_properly_on_system_streams_when_not_authenticated() => await _fixture.AppendStream("$sys-w-all"); + public async Task acl_inheritance_is_working_properly_on_system_streams_when_not_authenticated() => await Fixture.AppendStream("$sys-w-all"); - public class Fixture : SecurityFixture { + public class CustomFixture : SecurityFixture { protected override async Task When() { var settings = new SystemSettings( new(writeRole: "user1"), new(writeRole: "user1") ); - await Client.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + await Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( "user-no-acl", StreamState.NoStream, new(), userCredentials: TestCredentials.TestAdmin ); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( "user-w-diff", StreamState.NoStream, new(acl: new(writeRole: "user2")), userCredentials: TestCredentials.TestAdmin ); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( "user-w-multiple", StreamState.NoStream, new(acl: new(writeRoles: new[] { "user1", "user2" })), userCredentials: TestCredentials.TestAdmin ); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( "user-w-restricted", StreamState.NoStream, - new(acl: new(writeRoles: new string[0])), + new(acl: new(writeRoles: Array.Empty())), userCredentials: TestCredentials.TestAdmin ); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( "user-w-all", StreamState.NoStream, new(acl: new(writeRole: SystemRoles.All)), userCredentials: TestCredentials.TestAdmin ); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( "user-r-restricted", StreamState.NoStream, new(acl: new("user1")), userCredentials: TestCredentials.TestAdmin ); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( "$sys-no-acl", StreamState.NoStream, new(), userCredentials: TestCredentials.TestAdmin ); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( "$sys-w-diff", StreamState.NoStream, new(acl: new(writeRole: "user2")), userCredentials: TestCredentials.TestAdmin ); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( "$sys-w-multiple", StreamState.NoStream, new(acl: new(writeRoles: new[] { "user1", "user2" })), userCredentials: TestCredentials.TestAdmin ); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( "$sys-w-restricted", StreamState.NoStream, - new(acl: new(writeRoles: new string[0])), + new(acl: new(writeRoles: Array.Empty())), userCredentials: TestCredentials.TestAdmin ); - await Client.SetStreamMetadataAsync( + await Streams.SetStreamMetadataAsync( "$sys-w-all", StreamState.NoStream, new(acl: new(writeRole: SystemRoles.All)), diff --git a/test/EventStore.Client.Streams.Tests/Security/subscribe_to_all_security.cs b/test/EventStore.Client.Streams.Tests/Security/subscribe_to_all_security.cs index a8948b8e4..48b62a52d 100644 --- a/test/EventStore.Client.Streams.Tests/Security/subscribe_to_all_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/subscribe_to_all_security.cs @@ -1,28 +1,21 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class subscribe_to_all_security : IClassFixture { - readonly Fixture _fixture; - - public subscribe_to_all_security(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class subscribe_to_all_security(ITestOutputHelper output, SecurityFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task subscribing_to_all_with_not_existing_credentials_is_not_authenticated() => - await Assert.ThrowsAsync(() => _fixture.SubscribeToAll(TestCredentials.TestBadUser)); + await Assert.ThrowsAsync(() => Fixture.SubscribeToAll(TestCredentials.TestBadUser)); [Fact] - public async Task subscribing_to_all_with_no_credentials_is_denied() => await Assert.ThrowsAsync(() => _fixture.SubscribeToAll()); + public async Task subscribing_to_all_with_no_credentials_is_denied() => await Assert.ThrowsAsync(() => Fixture.SubscribeToAll()); [Fact] public async Task subscribing_to_all_with_not_authorized_user_credentials_is_denied() => - await Assert.ThrowsAsync(() => _fixture.SubscribeToAll(TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.SubscribeToAll(TestCredentials.TestUser2)); [Fact] - public async Task subscribing_to_all_with_authorized_user_credentials_succeeds() => await _fixture.SubscribeToAll(TestCredentials.TestUser1); + public async Task subscribing_to_all_with_authorized_user_credentials_succeeds() => await Fixture.SubscribeToAll(TestCredentials.TestUser1); [Fact] - public async Task subscribing_to_all_with_admin_user_credentials_succeeds() => await _fixture.SubscribeToAll(TestCredentials.TestAdmin); - - public class Fixture : SecurityFixture { - protected override Task When() => Task.CompletedTask; - } + public async Task subscribing_to_all_with_admin_user_credentials_succeeds() => await Fixture.SubscribeToAll(TestCredentials.TestAdmin); } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/subscribe_to_stream_security.cs b/test/EventStore.Client.Streams.Tests/Security/subscribe_to_stream_security.cs index 10f046629..1bcb43ae1 100644 --- a/test/EventStore.Client.Streams.Tests/Security/subscribe_to_stream_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/subscribe_to_stream_security.cs @@ -1,98 +1,74 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class subscribe_to_stream_security : IClassFixture { - readonly Fixture _fixture; - - public subscribe_to_stream_security(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class subscribe_to_stream_security(ITestOutputHelper output, SecurityFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task subscribing_to_stream_with_not_existing_credentials_is_not_authenticated() => - await Assert.ThrowsAsync( - () => - _fixture.SubscribeToStream(SecurityFixture.ReadStream, TestCredentials.TestBadUser) - ); + await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(SecurityFixture.ReadStream, TestCredentials.TestBadUser)); [Fact] public async Task subscribing_to_stream_with_no_credentials_is_denied() => - await Assert.ThrowsAsync( - () => - _fixture.SubscribeToStream(SecurityFixture.ReadStream) - ); + await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(SecurityFixture.ReadStream)); [Fact] public async Task subscribing_to_stream_with_not_authorized_user_credentials_is_denied() => - await Assert.ThrowsAsync( - () => - _fixture.SubscribeToStream(SecurityFixture.ReadStream, TestCredentials.TestUser2) - ); + await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(SecurityFixture.ReadStream, TestCredentials.TestUser2)); [Fact] public async Task reading_stream_with_authorized_user_credentials_succeeds() { - await _fixture.AppendStream(SecurityFixture.ReadStream, TestCredentials.TestUser1); - await _fixture.SubscribeToStream(SecurityFixture.ReadStream, TestCredentials.TestUser1); + await Fixture.AppendStream(SecurityFixture.ReadStream, TestCredentials.TestUser1); + await Fixture.SubscribeToStream(SecurityFixture.ReadStream, TestCredentials.TestUser1); } [Fact] public async Task reading_stream_with_admin_user_credentials_succeeds() { - await _fixture.AppendStream(SecurityFixture.ReadStream, TestCredentials.TestAdmin); - await _fixture.SubscribeToStream(SecurityFixture.ReadStream, TestCredentials.TestAdmin); + await Fixture.AppendStream(SecurityFixture.ReadStream, TestCredentials.TestAdmin); + await Fixture.SubscribeToStream(SecurityFixture.ReadStream, TestCredentials.TestAdmin); } [AnonymousAccess.Fact] public async Task subscribing_to_no_acl_stream_succeeds_when_no_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NoAclStream); - await _fixture.SubscribeToStream(SecurityFixture.NoAclStream); + await Fixture.AppendStream(SecurityFixture.NoAclStream); + await Fixture.SubscribeToStream(SecurityFixture.NoAclStream); } [Fact] public async Task subscribing_to_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed() => - await Assert.ThrowsAsync( - () => - _fixture.SubscribeToStream(SecurityFixture.NoAclStream, TestCredentials.TestBadUser) - ); + await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(SecurityFixture.NoAclStream, TestCredentials.TestBadUser)); [Fact] public async Task subscribing_to_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestUser1); - await _fixture.SubscribeToStream(SecurityFixture.NoAclStream, TestCredentials.TestUser1); - await _fixture.SubscribeToStream(SecurityFixture.NoAclStream, TestCredentials.TestUser2); + await Fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestUser1); + await Fixture.SubscribeToStream(SecurityFixture.NoAclStream, TestCredentials.TestUser1); + await Fixture.SubscribeToStream(SecurityFixture.NoAclStream, TestCredentials.TestUser2); } [Fact] public async Task subscribing_to_no_acl_stream_succeeds_when_admin_user_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); - await _fixture.SubscribeToStream(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); + await Fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); + await Fixture.SubscribeToStream(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); } [AnonymousAccess.Fact] public async Task subscribing_to_all_access_normal_stream_succeeds_when_no_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NormalAllStream); - await _fixture.SubscribeToStream(SecurityFixture.NormalAllStream); + await Fixture.AppendStream(SecurityFixture.NormalAllStream); + await Fixture.SubscribeToStream(SecurityFixture.NormalAllStream); } [Fact] - public async Task - subscribing_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() => - await Assert.ThrowsAsync( - () => - _fixture.SubscribeToStream(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser) - ); + public async Task subscribing_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() => + await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser)); [Fact] - public async Task - subscribing_to_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); - await _fixture.SubscribeToStream(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); - await _fixture.SubscribeToStream(SecurityFixture.NormalAllStream, TestCredentials.TestUser2); + public async Task subscribing_to_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed() { + await Fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); + await Fixture.SubscribeToStream(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); + await Fixture.SubscribeToStream(SecurityFixture.NormalAllStream, TestCredentials.TestUser2); } [Fact] public async Task subscribing_to_all_access_normal_streamm_succeeds_when_admin_user_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); - await _fixture.SubscribeToStream(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); - } - - public class Fixture : SecurityFixture { - protected override Task When() => Task.CompletedTask; + await Fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); + await Fixture.SubscribeToStream(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/system_stream_security.cs b/test/EventStore.Client.Streams.Tests/Security/system_stream_security.cs index 9b975de20..bbf482cbe 100644 --- a/test/EventStore.Client.Streams.Tests/Security/system_stream_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/system_stream_security.cs @@ -1,170 +1,145 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class system_stream_security : IClassFixture { - readonly Fixture _fixture; - - public system_stream_security(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class system_stream_security(ITestOutputHelper output, SecurityFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task operations_on_system_stream_with_no_acl_set_fail_for_non_admin() { - await Assert.ThrowsAsync(() => _fixture.ReadEvent("$system-no-acl", TestCredentials.TestUser1)); - await Assert.ThrowsAsync( - () => - _fixture.ReadStreamForward("$system-no-acl", TestCredentials.TestUser1) - ); + await Assert.ThrowsAsync(() => Fixture.ReadEvent("$system-no-acl", TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamForward("$system-no-acl", TestCredentials.TestUser1)); - await Assert.ThrowsAsync( - () => - _fixture.ReadStreamBackward("$system-no-acl", TestCredentials.TestUser1) - ); + await Assert.ThrowsAsync(() => Fixture.ReadStreamBackward("$system-no-acl", TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => _fixture.AppendStream("$system-no-acl", TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.AppendStream("$system-no-acl", TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => _fixture.ReadMeta("$system-no-acl", TestCredentials.TestUser1)); - await Assert.ThrowsAsync( - () => - _fixture.WriteMeta("$system-no-acl", TestCredentials.TestUser1) - ); + await Assert.ThrowsAsync(() => Fixture.ReadMeta("$system-no-acl", TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.WriteMeta("$system-no-acl", TestCredentials.TestUser1)); - await Assert.ThrowsAsync( - () => - _fixture.SubscribeToStream("$system-no-acl", TestCredentials.TestUser1) - ); + await Assert.ThrowsAsync(() => Fixture.SubscribeToStream("$system-no-acl", TestCredentials.TestUser1)); } [Fact] public async Task operations_on_system_stream_with_no_acl_set_succeed_for_admin() { - await _fixture.AppendStream("$system-no-acl", TestCredentials.TestAdmin); + await Fixture.AppendStream("$system-no-acl", TestCredentials.TestAdmin); - await _fixture.ReadEvent("$system-no-acl", TestCredentials.TestAdmin); - await _fixture.ReadStreamForward("$system-no-acl", TestCredentials.TestAdmin); - await _fixture.ReadStreamBackward("$system-no-acl", TestCredentials.TestAdmin); + await Fixture.ReadEvent("$system-no-acl", TestCredentials.TestAdmin); + await Fixture.ReadStreamForward("$system-no-acl", TestCredentials.TestAdmin); + await Fixture.ReadStreamBackward("$system-no-acl", TestCredentials.TestAdmin); - await _fixture.ReadMeta("$system-no-acl", TestCredentials.TestAdmin); - await _fixture.WriteMeta("$system-no-acl", TestCredentials.TestAdmin); + await Fixture.ReadMeta("$system-no-acl", TestCredentials.TestAdmin); + await Fixture.WriteMeta("$system-no-acl", TestCredentials.TestAdmin); - await _fixture.SubscribeToStream("$system-no-acl", TestCredentials.TestAdmin); + await Fixture.SubscribeToStream("$system-no-acl", TestCredentials.TestAdmin); } [Fact] public async Task operations_on_system_stream_with_acl_set_to_usual_user_fail_for_not_authorized_user() { - await Assert.ThrowsAsync(() => _fixture.ReadEvent(SecurityFixture.SystemAclStream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.ReadStreamForward(SecurityFixture.SystemAclStream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync( - () => - _fixture.ReadStreamBackward(SecurityFixture.SystemAclStream, TestCredentials.TestUser2) - ); + await Assert.ThrowsAsync(() => Fixture.ReadEvent(SecurityFixture.SystemAclStream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(SecurityFixture.SystemAclStream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamBackward(SecurityFixture.SystemAclStream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.AppendStream(SecurityFixture.SystemAclStream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.SystemAclStream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => _fixture.ReadMeta(SecurityFixture.SystemAclStream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync( - () => - _fixture.WriteMeta(SecurityFixture.SystemAclStream, TestCredentials.TestUser2, TestCredentials.TestUser1.Username) - ); + await Assert.ThrowsAsync(() => Fixture.ReadMeta(SecurityFixture.SystemAclStream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.WriteMeta(SecurityFixture.SystemAclStream, TestCredentials.TestUser2, TestCredentials.TestUser1.Username)); - await Assert.ThrowsAsync(() => _fixture.SubscribeToStream(SecurityFixture.SystemAclStream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(SecurityFixture.SystemAclStream, TestCredentials.TestUser2)); } [Fact] public async Task operations_on_system_stream_with_acl_set_to_usual_user_succeed_for_that_user() { - await _fixture.AppendStream(SecurityFixture.SystemAclStream, TestCredentials.TestUser1); - await _fixture.ReadEvent(SecurityFixture.SystemAclStream, TestCredentials.TestUser1); - await _fixture.ReadStreamForward(SecurityFixture.SystemAclStream, TestCredentials.TestUser1); - await _fixture.ReadStreamBackward(SecurityFixture.SystemAclStream, TestCredentials.TestUser1); + await Fixture.AppendStream(SecurityFixture.SystemAclStream, TestCredentials.TestUser1); + await Fixture.ReadEvent(SecurityFixture.SystemAclStream, TestCredentials.TestUser1); + await Fixture.ReadStreamForward(SecurityFixture.SystemAclStream, TestCredentials.TestUser1); + await Fixture.ReadStreamBackward(SecurityFixture.SystemAclStream, TestCredentials.TestUser1); - await _fixture.ReadMeta(SecurityFixture.SystemAclStream, TestCredentials.TestUser1); - await _fixture.WriteMeta(SecurityFixture.SystemAclStream, TestCredentials.TestUser1, TestCredentials.TestUser1.Username); + await Fixture.ReadMeta(SecurityFixture.SystemAclStream, TestCredentials.TestUser1); + await Fixture.WriteMeta(SecurityFixture.SystemAclStream, TestCredentials.TestUser1, TestCredentials.TestUser1.Username); - await _fixture.SubscribeToStream(SecurityFixture.SystemAclStream, TestCredentials.TestUser1); + await Fixture.SubscribeToStream(SecurityFixture.SystemAclStream, TestCredentials.TestUser1); } [Fact] public async Task operations_on_system_stream_with_acl_set_to_usual_user_succeed_for_admin() { - await _fixture.AppendStream(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin); - await _fixture.ReadEvent(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin); - await _fixture.ReadStreamForward(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin); - await _fixture.ReadStreamBackward(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin); + await Fixture.AppendStream(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin); + await Fixture.ReadEvent(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin); + await Fixture.ReadStreamForward(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin); + await Fixture.ReadStreamBackward(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin); - await _fixture.ReadMeta(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin); - await _fixture.WriteMeta(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin, TestCredentials.TestUser1.Username); + await Fixture.ReadMeta(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin); + await Fixture.WriteMeta(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin, TestCredentials.TestUser1.Username); - await _fixture.SubscribeToStream(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin); + await Fixture.SubscribeToStream(SecurityFixture.SystemAclStream, TestCredentials.TestAdmin); } [Fact] public async Task operations_on_system_stream_with_acl_set_to_admins_fail_for_usual_user() { - await Assert.ThrowsAsync(() => _fixture.ReadEvent(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => _fixture.ReadStreamForward(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.ReadEvent(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1)); await Assert.ThrowsAsync( () => - _fixture.ReadStreamBackward(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1) + Fixture.ReadStreamBackward(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1) ); - await Assert.ThrowsAsync(() => _fixture.AppendStream(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => _fixture.ReadMeta(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.ReadMeta(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1)); await Assert.ThrowsAsync( () => - _fixture.WriteMeta(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1, SystemRoles.Admins) + Fixture.WriteMeta(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1, SystemRoles.Admins) ); - await Assert.ThrowsAsync(() => _fixture.SubscribeToStream(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(SecurityFixture.SystemAdminStream, TestCredentials.TestUser1)); } [Fact] public async Task operations_on_system_stream_with_acl_set_to_admins_succeed_for_admin() { - await _fixture.AppendStream(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin); - await _fixture.ReadEvent(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin); - await _fixture.ReadStreamForward(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin); - await _fixture.ReadStreamBackward(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin); + await Fixture.AppendStream(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin); + await Fixture.ReadEvent(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin); + await Fixture.ReadStreamForward(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin); + await Fixture.ReadStreamBackward(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin); - await _fixture.ReadMeta(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin); - await _fixture.WriteMeta(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin, SystemRoles.Admins); + await Fixture.ReadMeta(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin); + await Fixture.WriteMeta(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin, SystemRoles.Admins); - await _fixture.SubscribeToStream(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin); + await Fixture.SubscribeToStream(SecurityFixture.SystemAdminStream, TestCredentials.TestAdmin); } [AnonymousAccess.Fact] public async Task operations_on_system_stream_with_acl_set_to_all_succeed_for_not_authenticated_user() { - await _fixture.AppendStream(SecurityFixture.SystemAllStream); - await _fixture.ReadEvent(SecurityFixture.SystemAllStream); - await _fixture.ReadStreamForward(SecurityFixture.SystemAllStream); - await _fixture.ReadStreamBackward(SecurityFixture.SystemAllStream); + await Fixture.AppendStream(SecurityFixture.SystemAllStream); + await Fixture.ReadEvent(SecurityFixture.SystemAllStream); + await Fixture.ReadStreamForward(SecurityFixture.SystemAllStream); + await Fixture.ReadStreamBackward(SecurityFixture.SystemAllStream); - await _fixture.ReadMeta(SecurityFixture.SystemAllStream); - await _fixture.WriteMeta(SecurityFixture.SystemAllStream, role: SystemRoles.All); + await Fixture.ReadMeta(SecurityFixture.SystemAllStream); + await Fixture.WriteMeta(SecurityFixture.SystemAllStream, role: SystemRoles.All); - await _fixture.SubscribeToStream(SecurityFixture.SystemAllStream); + await Fixture.SubscribeToStream(SecurityFixture.SystemAllStream); } [Fact] public async Task operations_on_system_stream_with_acl_set_to_all_succeed_for_usual_user() { - await _fixture.AppendStream(SecurityFixture.SystemAllStream, TestCredentials.TestUser1); - await _fixture.ReadEvent(SecurityFixture.SystemAllStream, TestCredentials.TestUser1); - await _fixture.ReadStreamForward(SecurityFixture.SystemAllStream, TestCredentials.TestUser1); - await _fixture.ReadStreamBackward(SecurityFixture.SystemAllStream, TestCredentials.TestUser1); + await Fixture.AppendStream(SecurityFixture.SystemAllStream, TestCredentials.TestUser1); + await Fixture.ReadEvent(SecurityFixture.SystemAllStream, TestCredentials.TestUser1); + await Fixture.ReadStreamForward(SecurityFixture.SystemAllStream, TestCredentials.TestUser1); + await Fixture.ReadStreamBackward(SecurityFixture.SystemAllStream, TestCredentials.TestUser1); - await _fixture.ReadMeta(SecurityFixture.SystemAllStream, TestCredentials.TestUser1); - await _fixture.WriteMeta(SecurityFixture.SystemAllStream, TestCredentials.TestUser1, SystemRoles.All); + await Fixture.ReadMeta(SecurityFixture.SystemAllStream, TestCredentials.TestUser1); + await Fixture.WriteMeta(SecurityFixture.SystemAllStream, TestCredentials.TestUser1, SystemRoles.All); - await _fixture.SubscribeToStream(SecurityFixture.SystemAllStream, TestCredentials.TestUser1); + await Fixture.SubscribeToStream(SecurityFixture.SystemAllStream, TestCredentials.TestUser1); } [Fact] public async Task operations_on_system_stream_with_acl_set_to_all_succeed_for_admin() { - await _fixture.AppendStream(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin); - await _fixture.ReadEvent(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin); - await _fixture.ReadStreamForward(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin); - await _fixture.ReadStreamBackward(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin); + await Fixture.AppendStream(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin); + await Fixture.ReadEvent(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin); + await Fixture.ReadStreamForward(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin); + await Fixture.ReadStreamBackward(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin); - await _fixture.ReadMeta(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin); - await _fixture.WriteMeta(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin, SystemRoles.All); - - await _fixture.SubscribeToStream(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin); - } + await Fixture.ReadMeta(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin); + await Fixture.WriteMeta(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin, SystemRoles.All); - public class Fixture : SecurityFixture { - protected override Task When() => Task.CompletedTask; + await Fixture.SubscribeToStream(SecurityFixture.SystemAllStream, TestCredentials.TestAdmin); } } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/write_stream_meta_security.cs b/test/EventStore.Client.Streams.Tests/Security/write_stream_meta_security.cs index 5a072214e..9b003795c 100644 --- a/test/EventStore.Client.Streams.Tests/Security/write_stream_meta_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/write_stream_meta_security.cs @@ -1,15 +1,12 @@ -namespace EventStore.Client.Streams.Tests.Security; - -public class write_stream_meta_security : IClassFixture { - readonly Fixture _fixture; - - public write_stream_meta_security(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests; +[Trait("Category", "Security")] +public class write_stream_meta_security(ITestOutputHelper output, SecurityFixture fixture) : EventStoreTests(output, fixture) { [Fact] public async Task writing_meta_with_not_existing_credentials_is_not_authenticated() => await Assert.ThrowsAsync( () => - _fixture.WriteMeta( + Fixture.WriteMeta( SecurityFixture.MetaWriteStream, TestCredentials.TestBadUser, TestCredentials.TestUser1.Username @@ -18,16 +15,12 @@ await Assert.ThrowsAsync( [Fact] public async Task writing_meta_to_stream_with_no_credentials_is_denied() => - await Assert.ThrowsAsync( - () => - _fixture.WriteMeta(SecurityFixture.MetaWriteStream, role: TestCredentials.TestUser1.Username) - ); + await Assert.ThrowsAsync(() => Fixture.WriteMeta(SecurityFixture.MetaWriteStream, role: TestCredentials.TestUser1.Username)); [Fact] public async Task writing_meta_to_stream_with_not_authorized_user_credentials_is_denied() => - await Assert.ThrowsAsync( - () => - _fixture.WriteMeta( + await Assert.ThrowsAsync(() => + Fixture.WriteMeta( SecurityFixture.MetaWriteStream, TestCredentials.TestUser2, TestCredentials.TestUser1.Username @@ -36,7 +29,7 @@ await Assert.ThrowsAsync( [Fact] public async Task writing_meta_to_stream_with_authorized_user_credentials_succeeds() => - await _fixture.WriteMeta( + await Fixture.WriteMeta( SecurityFixture.MetaWriteStream, TestCredentials.TestUser1, TestCredentials.TestUser1.Username @@ -44,57 +37,45 @@ await _fixture.WriteMeta( [Fact] public async Task writing_meta_to_stream_with_admin_user_credentials_succeeds() => - await _fixture.WriteMeta( + await Fixture.WriteMeta( SecurityFixture.MetaWriteStream, TestCredentials.TestAdmin, TestCredentials.TestUser1.Username ); [AnonymousAccess.Fact] - public async Task writing_meta_to_no_acl_stream_succeeds_when_no_credentials_are_passed() => await _fixture.WriteMeta(SecurityFixture.NoAclStream); + public async Task writing_meta_to_no_acl_stream_succeeds_when_no_credentials_are_passed() => await Fixture.WriteMeta(SecurityFixture.NoAclStream); [Fact] - public async Task - writing_meta_to_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed() => - await Assert.ThrowsAsync( - () => - _fixture.WriteMeta(SecurityFixture.NoAclStream, TestCredentials.TestBadUser) - ); + public async Task writing_meta_to_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed() => + await Assert.ThrowsAsync(() => Fixture.WriteMeta(SecurityFixture.NoAclStream, TestCredentials.TestBadUser)); [Fact] public async Task writing_meta_to_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed() { - await _fixture.WriteMeta(SecurityFixture.NoAclStream, TestCredentials.TestUser1); - await _fixture.WriteMeta(SecurityFixture.NoAclStream, TestCredentials.TestUser2); + await Fixture.WriteMeta(SecurityFixture.NoAclStream, TestCredentials.TestUser1); + await Fixture.WriteMeta(SecurityFixture.NoAclStream, TestCredentials.TestUser2); } [Fact] public async Task writing_meta_to_no_acl_stream_succeeds_when_admin_user_credentials_are_passed() => - await _fixture.WriteMeta(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); + await Fixture.WriteMeta(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); [AnonymousAccess.Fact] public async Task writing_meta_to_all_access_normal_stream_succeeds_when_no_credentials_are_passed() => - await _fixture.WriteMeta(SecurityFixture.NormalAllStream, role: SystemRoles.All); + await Fixture.WriteMeta(SecurityFixture.NormalAllStream, role: SystemRoles.All); [Fact] - public async Task - writing_meta_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() => - await Assert.ThrowsAsync( - () => - _fixture.WriteMeta(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser, SystemRoles.All) - ); + public async Task writing_meta_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() => + await Assert.ThrowsAsync(() => Fixture.WriteMeta(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser, SystemRoles.All)); [Fact] public async Task writing_meta_to_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed() { - await _fixture.WriteMeta(SecurityFixture.NormalAllStream, TestCredentials.TestUser1, SystemRoles.All); - await _fixture.WriteMeta(SecurityFixture.NormalAllStream, TestCredentials.TestUser2, SystemRoles.All); + await Fixture.WriteMeta(SecurityFixture.NormalAllStream, TestCredentials.TestUser1, SystemRoles.All); + await Fixture.WriteMeta(SecurityFixture.NormalAllStream, TestCredentials.TestUser2, SystemRoles.All); } [Fact] public async Task writing_meta_to_all_access_normal_stream_succeeds_when_admin_user_credentials_are_passed() => - await _fixture.WriteMeta(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin, SystemRoles.All); - - public class Fixture : SecurityFixture { - protected override Task When() => Task.CompletedTask; - } + await Fixture.WriteMeta(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin, SystemRoles.All); } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Security/write_stream_security.cs b/test/EventStore.Client.Streams.Tests/Security/write_stream_security.cs index 44ac88aa7..d002d37c9 100644 --- a/test/EventStore.Client.Streams.Tests/Security/write_stream_security.cs +++ b/test/EventStore.Client.Streams.Tests/Security/write_stream_security.cs @@ -1,89 +1,70 @@ -namespace EventStore.Client.Streams.Tests.Security; +namespace EventStore.Client.Streams.Tests; -public class write_stream_security : IClassFixture { - readonly Fixture _fixture; +[Trait("Category", "Security")] +public class write_stream_security : IClassFixture { + public write_stream_security(ITestOutputHelper output, SecurityFixture fixture) => Fixture = fixture.With(x => x.CaptureTestRun(output)); - public write_stream_security(Fixture fixture, ITestOutputHelper outputHelper) { - _fixture = fixture; - _fixture.CaptureLogs(outputHelper); - } + SecurityFixture Fixture { get; } [Fact] public async Task writing_to_all_is_never_allowed() { - await Assert.ThrowsAsync(() => _fixture.AppendStream(SecurityFixture.AllStream)); - await Assert.ThrowsAsync( - () => _fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestUser1) - ); - - await Assert.ThrowsAsync( - () => _fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestAdmin) - ); + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.AllStream)); + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestAdmin)); } [Fact] public async Task writing_with_not_existing_credentials_is_not_authenticated() => - await Assert.ThrowsAsync( - () => _fixture.AppendStream(SecurityFixture.WriteStream, TestCredentials.TestBadUser) - ); + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.WriteStream, TestCredentials.TestBadUser)); [Fact] public async Task writing_to_stream_with_no_credentials_is_denied() => - await Assert.ThrowsAsync(() => _fixture.AppendStream(SecurityFixture.WriteStream)); + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.WriteStream)); [Fact] public async Task writing_to_stream_with_not_authorized_user_credentials_is_denied() => - await Assert.ThrowsAsync( - () => _fixture.AppendStream(SecurityFixture.WriteStream, TestCredentials.TestUser2) - ); + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.WriteStream, TestCredentials.TestUser2)); [Fact] public async Task writing_to_stream_with_authorized_user_credentials_succeeds() => - await _fixture.AppendStream(SecurityFixture.WriteStream, TestCredentials.TestUser1); + await Fixture.AppendStream(SecurityFixture.WriteStream, TestCredentials.TestUser1); [Fact] public async Task writing_to_stream_with_admin_user_credentials_succeeds() => - await _fixture.AppendStream(SecurityFixture.WriteStream, TestCredentials.TestAdmin); + await Fixture.AppendStream(SecurityFixture.WriteStream, TestCredentials.TestAdmin); [AnonymousAccess.Fact] - public async Task writing_to_no_acl_stream_succeeds_when_no_credentials_are_passed() => await _fixture.AppendStream(SecurityFixture.NoAclStream); + public async Task writing_to_no_acl_stream_succeeds_when_no_credentials_are_passed() => await Fixture.AppendStream(SecurityFixture.NoAclStream); [Fact] public async Task writing_to_no_acl_stream_is_not_authenticated_when_not_existing_credentials_are_passed() => - await Assert.ThrowsAsync( - () => - _fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestBadUser) - ); + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestBadUser)); [Fact] public async Task writing_to_no_acl_stream_succeeds_when_any_existing_user_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestUser1); - await _fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestUser2); + await Fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestUser1); + await Fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestUser2); } [Fact] public async Task writing_to_no_acl_stream_succeeds_when_any_admin_user_credentials_are_passed() => - await _fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); + await Fixture.AppendStream(SecurityFixture.NoAclStream, TestCredentials.TestAdmin); [AnonymousAccess.Fact] public async Task writing_to_all_access_normal_stream_succeeds_when_no_credentials_are_passed() => - await _fixture.AppendStream(SecurityFixture.NormalAllStream); + await Fixture.AppendStream(SecurityFixture.NormalAllStream); [Fact] - public async Task - writing_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() => - await Assert.ThrowsAsync(() => _fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser)); + public async Task writing_to_all_access_normal_stream_is_not_authenticated_when_not_existing_credentials_are_passed() => + await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestBadUser)); [Fact] public async Task writing_to_all_access_normal_stream_succeeds_when_any_existing_user_credentials_are_passed() { - await _fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); - await _fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestUser2); + await Fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestUser1); + await Fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestUser2); } [Fact] public async Task writing_to_all_access_normal_stream_succeeds_when_any_admin_user_credentials_are_passed() => - await _fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); - - public class Fixture : SecurityFixture { - protected override Task When() => Task.CompletedTask; - } + await Fixture.AppendStream(SecurityFixture.NormalAllStream, TestCredentials.TestAdmin); } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/is_json.cs b/test/EventStore.Client.Streams.Tests/Serialization/is_json.cs similarity index 69% rename from test/EventStore.Client.Streams.Tests/is_json.cs rename to test/EventStore.Client.Streams.Tests/Serialization/is_json.cs index ac0fa8ae1..d2b44a918 100644 --- a/test/EventStore.Client.Streams.Tests/is_json.cs +++ b/test/EventStore.Client.Streams.Tests/Serialization/is_json.cs @@ -1,15 +1,10 @@ using System.Runtime.CompilerServices; using System.Text; -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -[Trait("Category", "LongRunning")] -public class is_json : IClassFixture { - readonly Fixture _fixture; - - public is_json(Fixture fixture) => _fixture = fixture; +namespace EventStore.Client.Streams.Tests.Serialization; +[Trait("Category", "Serialization")] +public class is_json(ITestOutputHelper output, EventStoreFixture fixture) : EventStoreTests(output, fixture) { public static IEnumerable TestCases() { var json = @"{""some"":""json""}"; @@ -36,9 +31,9 @@ public async Task is_preserved(bool isJson, string data, string metadata) { : Constants.Metadata.ContentTypes.ApplicationOctetStream ); - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, new[] { eventData }); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, new[] { eventData }); - var @event = await _fixture.Client + var @event = await Fixture.Streams .ReadStreamAsync( Direction.Forwards, stream, @@ -60,10 +55,5 @@ public async Task is_preserved(bool isJson, string data, string metadata) { } string GetStreamName(bool isJson, string data, string metadata, [CallerMemberName] string? testMethod = default) => - $"{_fixture.GetStreamName(testMethod)}_{isJson}_{(data == string.Empty ? "no_data" : "data")}_{(metadata == string.Empty ? "no_metadata" : "metadata")}"; - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } + $"{Fixture.GetStreamName(testMethod)}_{isJson}_{(data == string.Empty ? "no_data" : "data")}_{(metadata == string.Empty ? "no_metadata" : "metadata")}"; } \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Subscriptions/SubscriptionDroppedResult.cs b/test/EventStore.Client.Streams.Tests/Subscriptions/SubscriptionDroppedResult.cs new file mode 100644 index 000000000..40df3eb52 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Subscriptions/SubscriptionDroppedResult.cs @@ -0,0 +1,16 @@ +namespace EventStore.Client.Streams.Tests.Subscriptions; + +public record SubscriptionDroppedResult(SubscriptionDroppedReason Reason, Exception? Error) { + public Task Throw() => Task.FromException(Error!); + + public static SubscriptionDroppedResult ServerError(Exception? error = null) => + new(SubscriptionDroppedReason.ServerError, error ?? new Exception("Server error")); + + public static SubscriptionDroppedResult SubscriberError(Exception? error = null) => + new(SubscriptionDroppedReason.SubscriberError, error ?? new Exception("Subscriber error")); + + public static SubscriptionDroppedResult Disposed(Exception? error = null) => + new(SubscriptionDroppedReason.Disposed, error); + + public override string ToString() => $"{Reason} {Error?.Message ?? string.Empty}".Trim(); +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Subscriptions/SubscriptionFilter.cs b/test/EventStore.Client.Streams.Tests/Subscriptions/SubscriptionFilter.cs new file mode 100644 index 000000000..e670b69c9 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Subscriptions/SubscriptionFilter.cs @@ -0,0 +1,24 @@ +namespace EventStore.Client.Streams.Tests.Subscriptions; + +public record SubscriptionFilter(string Name, Func Create, Func PrepareEvent) { + public override string ToString() => Name; + + static readonly SubscriptionFilter StreamNamePrefix = new(nameof(StreamNamePrefix), StreamFilter.Prefix, (_, evt) => evt); + static readonly SubscriptionFilter StreamNameRegex = new(nameof(StreamNameRegex), f => StreamFilter.RegularExpression(f), (_, evt) => evt); + static readonly SubscriptionFilter EventTypePrefix = new(nameof(EventTypePrefix), EventTypeFilter.Prefix, (term, evt) => new(evt.EventId, term, evt.Data, evt.Metadata, evt.ContentType)); + static readonly SubscriptionFilter EventTypeRegex = new(nameof(EventTypeRegex), f => EventTypeFilter.RegularExpression(f), (term, evt) => new(evt.EventId, term, evt.Data, evt.Metadata, evt.ContentType)); + + static SubscriptionFilter() { + All = new[] { + StreamNamePrefix, + StreamNameRegex, + EventTypePrefix, + EventTypeRegex + }; + + TestCases = All.Select(x => new object[] { x }); + } + + public static SubscriptionFilter[] All { get; } + public static IEnumerable TestCases { get; } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Subscriptions/SubscriptionsFixture.cs b/test/EventStore.Client.Streams.Tests/Subscriptions/SubscriptionsFixture.cs new file mode 100644 index 000000000..582344f98 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Subscriptions/SubscriptionsFixture.cs @@ -0,0 +1,18 @@ +namespace EventStore.Client.Streams.Tests.Subscriptions; + + +[Trait("Category", "Subscriptions")] +public class SubscriptionsFixture : EventStoreFixture { + public SubscriptionsFixture(): base(x => x.RunProjections()) { + OnSetup = async () => { + await Streams.SetStreamMetadataAsync( + SystemStreams.AllStream, + StreamState.NoStream, + new(acl: new(SystemRoles.All)), + userCredentials: TestCredentials.Root + ); + + await Streams.AppendToStreamAsync($"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", StreamState.NoStream, CreateTestEvents(10)); + }; + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Subscriptions/reconnection.cs b/test/EventStore.Client.Streams.Tests/Subscriptions/reconnection.cs new file mode 100644 index 000000000..df9162130 --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Subscriptions/reconnection.cs @@ -0,0 +1,176 @@ +using Grpc.Core; +using static System.TimeSpan; + +namespace EventStore.Client.Streams.Tests.Subscriptions; + +[Trait("Category", "Subscriptions")] +public class @reconnection(ITestOutputHelper output, ReconnectionFixture fixture) : EventStoreTests(output, fixture) { + [Theory] + [InlineData(4, 1000, 0, 15000)] + public async Task when_the_connection_is_lost(int expectedNumberOfEvents, int reconnectDelayMs, int serviceRestartDelayMs, int testTimeoutMs) { + using var cancellator = new CancellationTokenSource().With(x => x.CancelAfter(testTimeoutMs)); + + var streamName = Fixture.GetStreamName(); + + // create backpressure by producing half of the events + await Fixture.ProduceEvents(streamName, expectedNumberOfEvents / 2, cancellationToken: cancellator.Token); + + // create subscription that will actually receive the first event and + // then wait for the service to be restarted + // but we are evil and will force the drop of the subscription muah ah ah + var consumeEvents = Fixture.ConsumeEvents( + streamName, + expectedNumberOfEvents, + FromMilliseconds(reconnectDelayMs), + cancellator.Token + ); + + // create chaos by pausing the service + await Fixture.RestartService(FromMilliseconds(serviceRestartDelayMs)); + + // produce the rest of the events to make it more interesting + await Fixture.ProduceEvents(streamName, expectedNumberOfEvents / 2, cancellationToken: cancellator.Token); + + // wait for the subscription to receive all events or timeout + await consumeEvents.ShouldNotThrowAsync(); + } +} + +public class ReconnectionFixture() + : EventStoreFixture( + x => x.RunInMemory(false) + .With(o => o.ClientSettings.ConnectivitySettings.DiscoveryInterval = FromMilliseconds(100)) + .With(o => o.ClientSettings.ConnectivitySettings.GossipTimeout = FromMilliseconds(100)) + ) +{ + public async Task ProduceEvents(string streamName, int numberOfEvents, StreamState? streamState = null, CancellationToken cancellationToken = default) { + while (!cancellationToken.IsCancellationRequested) { + try { + var result = await Streams.AppendToStreamAsync( + streamName, + streamState.GetValueOrDefault(StreamState.Any), + CreateTestEvents(numberOfEvents), + cancellationToken: cancellationToken + ); + + if (result is SuccessResult success) { + Log.Information( + "{NumberOfEvents} events produced to {StreamName}.", numberOfEvents, streamName + ); + + return; + } + + Log.Error( + "Failed to produce {NumberOfEvents} events to {StreamName}.", numberOfEvents, streamName + ); + + await Task.Delay(250); + } + catch (Exception ex) when ( ex is not OperationCanceledException) { + Log.Error( + ex, "Failed to produce {NumberOfEvents} events to {StreamName}.", numberOfEvents, streamName + ); + + await Task.Delay(250); + } + } + } + + public Task ConsumeEvents( + string streamName, + int expectedNumberOfEvents, + TimeSpan reconnectDelay, + CancellationToken cancellationToken + ) { + var receivedAllEvents = new TaskCompletionSource(); + + var receivedEventsCount = 0; + + _ = SubscribeToStream( + streamName, + checkpoint: null, + OnReceive(), + OnDrop(), + cancellationToken + ); + + return receivedAllEvents.Task; + + Func OnReceive() { + return re => { + receivedEventsCount++; + Log.Debug("{ReceivedEventsCount}/{ExpectedNumberOfEvents} events received.", receivedEventsCount, expectedNumberOfEvents); + + if (receivedEventsCount == expectedNumberOfEvents) { + Log.Information("Test complete. {ReceivedEventsCount}/{ExpectedNumberOfEvents} events received.", receivedEventsCount, expectedNumberOfEvents); + receivedAllEvents.TrySetResult(); + } + + return Task.CompletedTask; + }; + } + + Func> OnDrop() { + return async (reason, ex) => { + if (ex is RpcException { StatusCode: StatusCode.Unavailable or StatusCode.DeadlineExceeded }) { + Log.Warning("Transitive exception detected. Retrying connection in {reconnectDelayMs}ms.", reconnectDelay.TotalMilliseconds); + await Task.Delay(reconnectDelay); + return true; + } + + if (reason == SubscriptionDroppedReason.Disposed || ex is OperationCanceledException || ex is TaskCanceledException || ex is null) { + if (receivedEventsCount != expectedNumberOfEvents) + receivedAllEvents.TrySetException(new TimeoutException($"Test timeout detected. {receivedEventsCount}/{expectedNumberOfEvents} events received.", ex)); + else { + Log.Information("Test cancellation requested. {ReceivedEventsCount}/{ExpectedNumberOfEvents} events received.", receivedEventsCount, expectedNumberOfEvents); + receivedAllEvents.TrySetCanceled(cancellationToken); + } + + return false; + } + + Log.Fatal(ex, "Fatal exception detected. This is the end..."); + receivedAllEvents.SetException(ex); + + return false; + }; + } + } + + async Task SubscribeToStream( + string stream, + StreamPosition? checkpoint, + Func onReceive, + Func> onDrop, + CancellationToken cancellationToken + ) { + var start = checkpoint == null ? FromStream.Start : FromStream.After(checkpoint.Value); + + Log.Verbose("Attempting to start from checkpoint: {Checkpoint}.", checkpoint); + + try { + var sub = await Streams.SubscribeToStreamAsync( + streamName: stream, + start: start, + eventAppeared: async (s, re, ct) => { + await onReceive(re); + checkpoint = re.OriginalEventNumber; + Log.Verbose("Checkpoint Set: {Checkpoint}.", checkpoint); + }, + subscriptionDropped: async (s, reason, ex) => { + var resubscribe = await onDrop(reason, ex); + if (resubscribe) _ = SubscribeToStream(stream, checkpoint, onReceive, onDrop, cancellationToken); + }, + cancellationToken: cancellationToken + ); + } catch (Exception ex) { + var reason = ex is OperationCanceledException or TaskCanceledException + ? SubscriptionDroppedReason.Disposed + : SubscriptionDroppedReason.SubscriberError; + + var resubscribe = await onDrop(reason, ex); + if (resubscribe) _ = SubscribeToStream(stream, checkpoint, onReceive, onDrop, cancellationToken); + } + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Subscriptions/subscribe_to_all.cs b/test/EventStore.Client.Streams.Tests/Subscriptions/subscribe_to_all.cs new file mode 100644 index 000000000..9ea4add2b --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Subscriptions/subscribe_to_all.cs @@ -0,0 +1,591 @@ +namespace EventStore.Client.Streams.Tests.Subscriptions; + +[Trait("Category", "Subscriptions")] +[Trait("Category", "Target:All")] +public class subscribe_to_all(ITestOutputHelper output, SubscriptionsFixture fixture) : EventStoreTests(output, fixture) { + [Fact] + public async Task receives_all_events_from_start() { + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + var pageSize = seedEvents.Length / 2; + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + foreach (var evt in seedEvents.Take(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(FromAll.Start, OnReceived, false, OnDropped) + .WithTimeout(); + + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task receives_all_events_from_end() { + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(FromAll.End, OnReceived, false, OnDropped) + .WithTimeout(); + + // add the events we want to receive after we start the subscription + foreach (var evt in seedEvents) + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task receives_all_events_from_position() { + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + var pageSize = seedEvents.Length / 2; + + // only the second half of the events will be received + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + + IWriteResult writeResult = new SuccessResult(); + foreach (var evt in seedEvents.Take(pageSize)) + writeResult = await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + var position = FromAll.After(writeResult.LogPosition); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(position, OnReceived, false, OnDropped) + .WithTimeout(); + + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", pageSize); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task receives_all_events_with_resolved_links() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(3).ToArray(); + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(FromAll.Start, OnReceived, true, OnDropped) + .WithTimeout(); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + var hasResolvedLink = re.OriginalEvent.EventStreamId.StartsWith($"$et-{EventStoreFixture.TestEventType}"); + if (availableEvents.RemoveWhere(x => x == re.Event.EventId && hasResolvedLink) == 0) { + Fixture.Log.Debug("Received unexpected event {EventId} from stream {StreamId}", re.Event.EventId, re.OriginalEvent.EventStreamId); + return Task.CompletedTask; + } + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Theory] + [MemberData(nameof(SubscriptionFilter.TestCases), MemberType= typeof(SubscriptionFilter))] + public async Task receives_all_filtered_events_from_start(SubscriptionFilter filter) { + var streamPrefix = $"{nameof(receives_all_filtered_events_from_start)}-{filter.Name}-{Guid.NewGuid():N}"; + + Fixture.Log.Information("Using filter {FilterName} with prefix {StreamPrefix}", filter.Name, streamPrefix); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + var checkpointReached = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(64) + .Select(evt => filter.PrepareEvent(streamPrefix, evt)) + .ToArray(); + + var pageSize = seedEvents.Length / 2; + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + // add noise + await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents(3)); + + var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start).CountAsync(); + Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); + + // Debugging: + // await foreach (var evt in Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start)) + // Fixture.Log.Debug("Read event {EventId} from {StreamId}.", evt.OriginalEvent.EventId, evt.OriginalEvent.EventStreamId); + + // add some of the events we want to see before we start the subscription + foreach (var evt in seedEvents.Take(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1, CheckpointReached); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(FromAll.Start, OnReceived, false, OnDropped, filterOptions) + .WithTimeout(); + + // add some of the events we want to see after we start the subscription + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + // wait until all events were received and at least one checkpoint was reached? + await receivedAllEvents.Task.WithTimeout(); + await checkpointReached.Task.WithTimeout(); + + // await Task.WhenAll(receivedAllEvents.Task, checkpointReached.Task).WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + if (availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId) == 0) { + Fixture.Log.Error( + "Received unexpected event {EventId} from {StreamId}", + re.OriginalEvent.EventId, + re.OriginalEvent.EventStreamId + ); + + receivedAllEvents.TrySetException( + new InvalidOperationException($"Received unexpected event {re.OriginalEvent.EventId} from stream {re.OriginalEvent.EventStreamId}") + ); + } + else { + Fixture.Log.Verbose("Received expected event {EventId} from {StreamId}.", re.OriginalEvent.EventId, re.OriginalEvent.EventStreamId); + } + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events.", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) { + subscriptionDropped.SetResult(new(reason, ex)); + if (reason != SubscriptionDroppedReason.Disposed) { + receivedAllEvents.TrySetException(ex!); + checkpointReached.TrySetException(ex!); + } + } + + Task CheckpointReached(StreamSubscription sub, Position position, CancellationToken ct) { + Fixture.Log.Verbose( + "Checkpoint reached {Position}. Received {ReceivedEventsCount}/{TotalEventsCount} events", + position, seedEvents.Length - availableEvents.Count, seedEvents.Length + ); + checkpointReached.TrySetResult(true); + return Task.CompletedTask; + } + } + + [Theory] + [MemberData(nameof(SubscriptionFilter.TestCases), MemberType= typeof(SubscriptionFilter))] + public async Task receives_all_filtered_events_from_end(SubscriptionFilter filter) { + var streamPrefix = $"{nameof(receives_all_filtered_events_from_end)}-{filter.Name}-{Guid.NewGuid():N}"; + + Fixture.Log.Information("Using filter {FilterName} with prefix {StreamPrefix}", filter.Name, streamPrefix); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + var checkpointReached = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(64) + .Select(evt => filter.PrepareEvent(streamPrefix, evt)) + .ToArray(); + + var pageSize = seedEvents.Length / 2; + + // only the second half of the events will be received + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + + // add noise + await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents(3)); + + var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start).CountAsync(); + Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); + + // add some of the events that are a match to the filter but will not be received + foreach (var evt in seedEvents.Take(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1, CheckpointReached); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(FromAll.End, OnReceived, false, OnDropped, filterOptions) + .WithTimeout(); + + // add the events we want to receive after we start the subscription + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + // wait until all events were received and at least one checkpoint was reached? + await receivedAllEvents.Task.WithTimeout(); + await checkpointReached.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + if (availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId) == 0) { + Fixture.Log.Error( + "Received unexpected event {EventId} from {StreamId}", + re.OriginalEvent.EventId, + re.OriginalEvent.EventStreamId + ); + + receivedAllEvents.TrySetException( + new InvalidOperationException($"Received unexpected event {re.OriginalEvent.EventId} from stream {re.OriginalEvent.EventStreamId}") + ); + } + else { + Fixture.Log.Verbose("Received expected event {EventId} from {StreamId}", re.OriginalEvent.EventId, re.OriginalEvent.EventStreamId); + } + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", pageSize); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) { + subscriptionDropped.SetResult(new(reason, ex)); + if (reason != SubscriptionDroppedReason.Disposed) { + receivedAllEvents.TrySetException(ex!); + checkpointReached.TrySetException(ex!); + } + } + + Task CheckpointReached(StreamSubscription sub, Position position, CancellationToken ct) { + Fixture.Log.Verbose( + "Checkpoint reached {Position}. Received {ReceivedEventsCount}/{TotalEventsCount} events", + position, pageSize - availableEvents.Count, pageSize + ); + checkpointReached.TrySetResult(true); + return Task.CompletedTask; + } + } + + [Theory] + [MemberData(nameof(SubscriptionFilter.TestCases), MemberType= typeof(SubscriptionFilter))] + public async Task receives_all_filtered_events_from_position(SubscriptionFilter filter) { + var streamPrefix = $"{nameof(receives_all_filtered_events_from_position)}-{filter.Name}-{Guid.NewGuid():N}"; + + Fixture.Log.Information("Using filter {FilterName} with prefix {StreamPrefix}", filter.Name, streamPrefix); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + var checkpointReached = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(64) + .Select(evt => filter.PrepareEvent(streamPrefix, evt)) + .ToArray(); + + var pageSize = seedEvents.Length / 2; + + // only the second half of the events will be received + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + + // add noise + await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents(3)); + + var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start).CountAsync(); + Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); + + // add some of the events that are a match to the filter but will not be received + IWriteResult writeResult = new SuccessResult(); + foreach (var evt in seedEvents.Take(pageSize)) + writeResult = await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + var position = FromAll.After(writeResult.LogPosition); + + var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1, CheckpointReached); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(position, OnReceived, false, OnDropped, filterOptions) + .WithTimeout(); + + // add the events we want to receive after we start the subscription + foreach (var evt in seedEvents.Skip(pageSize)) + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + + // wait until all events were received and at least one checkpoint was reached? + await receivedAllEvents.Task.WithTimeout(); + await checkpointReached.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + if (availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId) == 0) { + Fixture.Log.Error( + "Received unexpected event {EventId} from {StreamId}", + re.OriginalEvent.EventId, + re.OriginalEvent.EventStreamId + ); + + receivedAllEvents.TrySetException( + new InvalidOperationException($"Received unexpected event {re.OriginalEvent.EventId} from stream {re.OriginalEvent.EventStreamId}") + ); + } + else { + Fixture.Log.Verbose("Received expected event {EventId} from {StreamId}", re.OriginalEvent.EventId, re.OriginalEvent.EventStreamId); + } + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", pageSize); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) { + subscriptionDropped.SetResult(new(reason, ex)); + if (reason != SubscriptionDroppedReason.Disposed) { + receivedAllEvents.TrySetException(ex!); + checkpointReached.TrySetException(ex!); + } + } + + Task CheckpointReached(StreamSubscription sub, Position position, CancellationToken ct) { + Fixture.Log.Verbose( + "Checkpoint reached {Position}. Received {ReceivedEventsCount}/{TotalEventsCount} events", + position, pageSize - availableEvents.Count, pageSize + ); + checkpointReached.TrySetResult(true); + return Task.CompletedTask; + } + } + + [Fact] + public async Task receives_all_filtered_events_with_resolved_links() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(3).ToArray(); + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); + + var options = new SubscriptionFilterOptions( + StreamFilter.Prefix($"$et-{EventStoreFixture.TestEventType}") + ); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync(FromAll.Start, OnReceived, true, OnDropped, options) + .WithTimeout(); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + var hasResolvedLink = re.OriginalEvent.EventStreamId.StartsWith($"$et-{EventStoreFixture.TestEventType}"); + if (availableEvents.RemoveWhere(x => x == re.Event.EventId && hasResolvedLink) == 0) { + Fixture.Log.Debug("Received unexpected event {EventId} from stream {StreamId}", re.Event.EventId, re.OriginalEvent.EventStreamId); + return Task.CompletedTask; + } + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task drops_when_disposed() { + var subscriptionDropped = new TaskCompletionSource(); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync( + FromAll.Start, + (sub, re, ct) => Task.CompletedTask, + false, + (sub, reason, ex) => subscriptionDropped.SetResult(new(reason, ex)) + ) + .WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + } + + [Fact] + public async Task drops_when_subscriber_error() { + var expectedResult = SubscriptionDroppedResult.SubscriberError(); + + var subscriptionDropped = new TaskCompletionSource(); + + using var subscription = await Fixture.Streams + .SubscribeToAllAsync( + FromAll.Start, + (sub, re, ct) => expectedResult.Throw(), + false, + (sub, reason, ex) => subscriptionDropped.SetResult(new(reason, ex)) + ) + .WithTimeout(); + + await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents()); + + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(expectedResult); + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/Subscriptions/subscribe_to_stream.cs b/test/EventStore.Client.Streams.Tests/Subscriptions/subscribe_to_stream.cs new file mode 100644 index 000000000..ca5dc122e --- /dev/null +++ b/test/EventStore.Client.Streams.Tests/Subscriptions/subscribe_to_stream.cs @@ -0,0 +1,302 @@ +namespace EventStore.Client.Streams.Tests.Subscriptions; + +[Trait("Category", "Subscriptions")] +[Trait("Category", "Target:Stream")] +public class subscribe_to_stream(ITestOutputHelper output, SubscriptionsFixture fixture) : EventStoreTests(output, fixture) { + [Fact] + public async Task receives_all_events_from_start() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + var pageSize = seedEvents.Length / 2; + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync(streamName, FromStream.Start, OnReceived, false, OnDropped) + .WithTimeout(); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.StreamExists, seedEvents.Skip(pageSize)); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task receives_all_events_from_position() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + var pageSize = seedEvents.Length / 2; + + // only the second half of the events will be received + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + + var writeResult = await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); + var streamPosition = StreamPosition.FromStreamRevision(writeResult.NextExpectedStreamRevision); + var checkpoint = FromStream.After(streamPosition); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync(streamName, checkpoint, OnReceived, false, OnDropped) + .WithTimeout(); + + await Fixture.Streams.AppendToStreamAsync(streamName, writeResult.NextExpectedStreamRevision, seedEvents.Skip(pageSize)); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", pageSize); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task receives_all_events_from_non_existing_stream() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(10).ToArray(); + + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync(streamName, FromStream.Start, OnReceived, false, OnDropped) + .WithTimeout(); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + availableEvents.RemoveWhere(x => x == re.OriginalEvent.EventId); + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } + + [Fact] + public async Task allow_multiple_subscriptions_to_same_stream() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(5).ToArray(); + + var targetEventsCount = seedEvents.Length * 2; + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); + + using var subscription1 = await Fixture.Streams + .SubscribeToStreamAsync(streamName, FromStream.Start, OnReceived) + .WithTimeout(); + + using var subscription2 = await Fixture.Streams + .SubscribeToStreamAsync(streamName, FromStream.Start, OnReceived) + .WithTimeout(); + + await receivedAllEvents.Task.WithTimeout(); + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + if (--targetEventsCount == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + } + + [Fact] + public async Task drops_when_disposed() { + var streamName = Fixture.GetStreamName(); + + var subscriptionDropped = new TaskCompletionSource(); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync( + streamName, + FromStream.Start, + (sub, re, ct) => Task.CompletedTask, + false, + (sub, reason, ex) => subscriptionDropped.SetResult(new(reason, ex)) + ) + .WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + } + + [Fact] + public async Task drops_when_subscriber_error() { + var streamName = Fixture.GetStreamName(); + + var expectedResult = SubscriptionDroppedResult.SubscriberError(); + + var subscriptionDropped = new TaskCompletionSource(); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync( + streamName, + FromStream.Start, + (sub, re, ct) => expectedResult.Throw(), + false, + (sub, reason, ex) => subscriptionDropped.SetResult(new(reason, ex)) + ) + .WithTimeout(); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, Fixture.CreateTestEvents()); + + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(expectedResult); + } + + [Fact] + public async Task drops_when_stream_tombstoned() { + var streamName = Fixture.GetStreamName(); + + var subscriptionDropped = new TaskCompletionSource(); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync( + streamName, + FromStream.Start, + (sub, re, ct) => Task.CompletedTask, + false, + (sub, reason, ex) => { subscriptionDropped.SetResult(new(reason, ex)); } + ) + .WithTimeout(); + + // rest in peace + await Fixture.Streams.TombstoneAsync(streamName, StreamState.NoStream); + + var result = await subscriptionDropped.Task.WithTimeout(); + result.Error.ShouldBeOfType().Stream.ShouldBe(streamName); + } + + [Fact] + public async Task receives_all_events_with_resolved_links() { + var streamName = Fixture.GetStreamName(); + + var receivedAllEvents = new TaskCompletionSource(); + var subscriptionDropped = new TaskCompletionSource(); + + var seedEvents = Fixture.CreateTestEvents(3).ToArray(); + var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + + await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); + + using var subscription = await Fixture.Streams + .SubscribeToStreamAsync($"$et-{EventStoreFixture.TestEventType}", FromStream.Start, OnReceived, true, OnDropped) + .WithTimeout(); + + await receivedAllEvents.Task.WithTimeout(); + + // if the subscription dropped before time, raise the reason why + if (subscriptionDropped.Task.IsCompleted) + subscriptionDropped.Task.IsCompleted.ShouldBe(false, subscriptionDropped.Task.Result.ToString()); + + // stop the subscription + subscription.Dispose(); + var result = await subscriptionDropped.Task.WithTimeout(); + result.ShouldBe(SubscriptionDroppedResult.Disposed()); + + return; + + Task OnReceived(StreamSubscription sub, ResolvedEvent re, CancellationToken ct) { + var hasResolvedLink = re.OriginalEvent.EventStreamId.StartsWith($"$et-{EventStoreFixture.TestEventType}"); + if (availableEvents.RemoveWhere(x => x == re.Event.EventId && hasResolvedLink) == 0) { + Fixture.Log.Debug("Received unexpected event {EventId} from stream {StreamId}", re.Event.EventId, re.OriginalEvent.EventStreamId); + return Task.CompletedTask; + } + + if (availableEvents.Count == 0) { + receivedAllEvents.TrySetResult(true); + Fixture.Log.Information("Received all {TotalEventsCount} expected events", seedEvents.Length); + } + + return Task.CompletedTask; + } + + void OnDropped(StreamSubscription sub, SubscriptionDroppedReason reason, Exception? ex) => + subscriptionDropped.SetResult(new(reason, ex)); + } +} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/TestEventExtensions.cs b/test/EventStore.Client.Streams.Tests/TestEventExtensions.cs deleted file mode 100644 index 1dfa8f298..000000000 --- a/test/EventStore.Client.Streams.Tests/TestEventExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -public static class TestEventExtensions { - public static IEnumerable AsResolvedTestEvents(this IEnumerable events) { - if (events == null) - throw new ArgumentNullException(nameof(events)); - - return events.Where(x => x.Event.EventType == EventStoreClientFixtureBase.TestEventType).Select(x => x.Event); - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/append_to_stream.cs b/test/EventStore.Client.Streams.Tests/append_to_stream.cs deleted file mode 100644 index 5ab7f9f02..000000000 --- a/test/EventStore.Client.Streams.Tests/append_to_stream.cs +++ /dev/null @@ -1,421 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class append_to_stream : IClassFixture { - readonly Fixture _fixture; - - public append_to_stream(Fixture fixture) => _fixture = fixture; - - public static IEnumerable ExpectedVersionCreateStreamTestCases() { - yield return new object?[] { StreamState.Any }; - yield return new object?[] { StreamState.NoStream }; - } - - [Theory] - [MemberData(nameof(ExpectedVersionCreateStreamTestCases))] - public async Task appending_zero_events(StreamState expectedStreamState) { - var stream = $"{_fixture.GetStreamName()}_{expectedStreamState}"; - - const int iterations = 2; - for (var i = 0; i < iterations; i++) { - var writeResult = await _fixture.Client.AppendToStreamAsync(stream, expectedStreamState, Enumerable.Empty()); - Assert.Equal(StreamRevision.None, writeResult.NextExpectedStreamRevision); - } - - var ex = await Assert.ThrowsAsync( - () => - _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, iterations) - .ToArrayAsync().AsTask() - ); - - Assert.Equal(stream, ex.Stream); - } - - [Theory] - [MemberData(nameof(ExpectedVersionCreateStreamTestCases))] - public async Task appending_zero_events_again(StreamState expectedStreamState) { - var stream = $"{_fixture.GetStreamName()}_{expectedStreamState}"; - - const int iterations = 2; - for (var i = 0; i < iterations; i++) { - var writeResult = await _fixture.Client.AppendToStreamAsync(stream, expectedStreamState, Enumerable.Empty()); - Assert.Equal(StreamRevision.None, writeResult.NextExpectedStreamRevision); - } - - var ex = await Assert.ThrowsAsync( - () => - _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, iterations) - .ToArrayAsync().AsTask() - ); - - Assert.Equal(stream, ex.Stream); - } - - [Theory] - [MemberData(nameof(ExpectedVersionCreateStreamTestCases))] - public async Task create_stream_expected_version_on_first_write_if_does_not_exist(StreamState expectedStreamState) { - var stream = $"{_fixture.GetStreamName()}_{expectedStreamState}"; - - var writeResult = await _fixture.Client.AppendToStreamAsync( - stream, - expectedStreamState, - _fixture.CreateTestEvents(1) - ); - - Assert.Equal(new(0), writeResult.NextExpectedStreamRevision); - - var count = await _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 2) - .CountAsync(); - - Assert.Equal(1, count); - } - - [Fact] - public async Task multiple_idempotent_writes() { - var stream = _fixture.GetStreamName(); - var events = _fixture.CreateTestEvents(4).ToArray(); - - var writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, events); - Assert.Equal(new(3), writeResult.NextExpectedStreamRevision); - - writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, events); - Assert.Equal(new(3), writeResult.NextExpectedStreamRevision); - } - - [Fact] - public async Task multiple_idempotent_writes_with_same_id_bug_case() { - var stream = _fixture.GetStreamName(); - - var evnt = _fixture.CreateTestEvents().First(); - var events = new[] { evnt, evnt, evnt, evnt, evnt, evnt }; - - var writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, events); - - Assert.Equal(new(5), writeResult.NextExpectedStreamRevision); - } - - [Fact] - public async Task - in_case_where_multiple_writes_of_multiple_events_with_the_same_ids_using_expected_version_any_then_next_expected_version_is_unreliable() { - var stream = _fixture.GetStreamName(); - - var evnt = _fixture.CreateTestEvents().First(); - var events = new[] { evnt, evnt, evnt, evnt, evnt, evnt }; - - var writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, events); - - Assert.Equal(new(5), writeResult.NextExpectedStreamRevision); - - writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, events); - - Assert.Equal(new(0), writeResult.NextExpectedStreamRevision); - } - - [Fact] - public async Task - in_case_where_multiple_writes_of_multiple_events_with_the_same_ids_using_expected_version_nostream_then_next_expected_version_is_correct() { - var stream = _fixture.GetStreamName(); - - var evnt = _fixture.CreateTestEvents().First(); - var events = new[] { evnt, evnt, evnt, evnt, evnt, evnt }; - var streamRevision = StreamRevision.FromInt64(events.Length - 1); - - var writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - Assert.Equal(streamRevision, writeResult.NextExpectedStreamRevision); - - writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - Assert.Equal(streamRevision, writeResult.NextExpectedStreamRevision); - } - - [Fact] - public async Task writing_with_correct_expected_version_to_deleted_stream_throws_stream_deleted() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.TombstoneAsync(stream, StreamState.NoStream); - - await Assert.ThrowsAsync( - () => _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - _fixture.CreateTestEvents(1) - ) - ); - } - - [Fact] - public async Task returns_log_position_when_writing() { - var stream = _fixture.GetStreamName(); - - var result = await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - _fixture.CreateTestEvents(1) - ); - - Assert.True(0 < result.LogPosition.PreparePosition); - Assert.True(0 < result.LogPosition.CommitPosition); - } - - [Fact] - public async Task writing_with_any_expected_version_to_deleted_stream_throws_stream_deleted() { - var stream = _fixture.GetStreamName(); - await _fixture.Client.TombstoneAsync(stream, StreamState.NoStream); - - await Assert.ThrowsAsync(() => _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, _fixture.CreateTestEvents(1))); - } - - [Fact] - public async Task writing_with_invalid_expected_version_to_deleted_stream_throws_stream_deleted() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.TombstoneAsync(stream, StreamState.NoStream); - - await Assert.ThrowsAsync(() => _fixture.Client.AppendToStreamAsync(stream, new StreamRevision(5), _fixture.CreateTestEvents())); - } - - [Fact] - public async Task append_with_correct_expected_version_to_existing_stream() { - var stream = _fixture.GetStreamName(); - - var writeResult = await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - _fixture.CreateTestEvents(1) - ); - - writeResult = await _fixture.Client.AppendToStreamAsync( - stream, - writeResult.NextExpectedStreamRevision, - _fixture.CreateTestEvents() - ); - - Assert.Equal(new(1), writeResult.NextExpectedStreamRevision); - } - - [Fact] - public async Task append_with_any_expected_version_to_existing_stream() { - var stream = _fixture.GetStreamName(); - - var writeResult = await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - _fixture.CreateTestEvents(1) - ); - - Assert.Equal(new(0), writeResult.NextExpectedStreamRevision); - - writeResult = await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.Any, - _fixture.CreateTestEvents(1) - ); - - Assert.Equal(new(1), writeResult.NextExpectedStreamRevision); - } - - [Fact] - public async Task appending_with_wrong_expected_version_to_existing_stream_throws_wrong_expected_version() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - - var ex = await Assert.ThrowsAsync( - () => _fixture.Client.AppendToStreamAsync(stream, new StreamRevision(999), _fixture.CreateTestEvents()) - ); - - Assert.Equal(new(0), ex.ActualStreamRevision); - Assert.Equal(new(999), ex.ExpectedStreamRevision); - } - - [Fact] - public async Task appending_with_wrong_expected_version_to_existing_stream_returns_wrong_expected_version() { - var stream = _fixture.GetStreamName(); - - var writeResult = await _fixture.Client.AppendToStreamAsync( - stream, - new StreamRevision(1), - _fixture.CreateTestEvents(), - options => { options.ThrowOnAppendFailure = false; } - ); - - var wrongExpectedVersionResult = (WrongExpectedVersionResult)writeResult; - - Assert.Equal(new(1), wrongExpectedVersionResult.NextExpectedStreamRevision); - } - - [Fact] - public async Task append_with_stream_exists_expected_version_to_existing_stream() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - - await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.StreamExists, - _fixture.CreateTestEvents() - ); - } - - [Fact] - public async Task append_with_stream_exists_expected_version_to_stream_with_multiple_events() { - var stream = _fixture.GetStreamName(); - - for (var i = 0; i < 5; i++) - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, _fixture.CreateTestEvents(1)); - - await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.StreamExists, - _fixture.CreateTestEvents() - ); - } - - [Fact] - public async Task append_with_stream_exists_expected_version_if_metadata_stream_exists() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.SetStreamMetadataAsync( - stream, - StreamState.Any, - new(10, default) - ); - - await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.StreamExists, - _fixture.CreateTestEvents() - ); - } - - [Fact] - public async Task - appending_with_stream_exists_expected_version_and_stream_does_not_exist_throws_wrong_expected_version() { - var stream = _fixture.GetStreamName(); - - var ex = await Assert.ThrowsAsync( - () => _fixture.Client.AppendToStreamAsync( - stream, - StreamState.StreamExists, - _fixture.CreateTestEvents() - ) - ); - - Assert.Equal(StreamRevision.None, ex.ActualStreamRevision); - } - - [Fact] - public async Task - appending_with_stream_exists_expected_version_and_stream_does_not_exist_returns_wrong_expected_version() { - var stream = _fixture.GetStreamName(); - - var writeResult = await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.StreamExists, - _fixture.CreateTestEvents(), - options => { options.ThrowOnAppendFailure = false; } - ); - - var wrongExpectedVersionResult = Assert.IsType(writeResult); - - Assert.Equal(StreamRevision.None, wrongExpectedVersionResult.NextExpectedStreamRevision); - } - - [Fact] - public async Task appending_with_stream_exists_expected_version_to_hard_deleted_stream_throws_stream_deleted() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.TombstoneAsync(stream, StreamState.NoStream); - - await Assert.ThrowsAsync( - () => _fixture.Client.AppendToStreamAsync( - stream, - StreamState.StreamExists, - _fixture.CreateTestEvents() - ) - ); - } - - [Fact] - public async Task appending_with_stream_exists_expected_version_to_deleted_stream_throws_stream_deleted() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - - await _fixture.Client.DeleteAsync(stream, StreamState.Any); - - await Assert.ThrowsAsync( - () => _fixture.Client.AppendToStreamAsync( - stream, - StreamState.StreamExists, - _fixture.CreateTestEvents() - ) - ); - } - - [Fact] - public async Task can_append_multiple_events_at_once() { - var stream = _fixture.GetStreamName(); - - var writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents(100)); - - Assert.Equal(new(99), writeResult.NextExpectedStreamRevision); - } - - [Fact] - public async Task returns_failure_status_when_conditionally_appending_with_version_mismatch() { - var stream = _fixture.GetStreamName(); - - var result = await _fixture.Client.ConditionalAppendToStreamAsync( - stream, - new StreamRevision(7), - _fixture.CreateTestEvents() - ); - - Assert.Equal( - ConditionalWriteResult.FromWrongExpectedVersion(new(stream, new StreamRevision(7), StreamRevision.None)), - result - ); - } - - [Fact] - public async Task returns_success_status_when_conditionally_appending_with_matching_version() { - var stream = _fixture.GetStreamName(); - - var result = await _fixture.Client.ConditionalAppendToStreamAsync( - stream, - StreamState.Any, - _fixture.CreateTestEvents() - ); - - Assert.Equal( - ConditionalWriteResult.FromWriteResult(new SuccessResult(0, result.LogPosition)), - result - ); - } - - [Fact] - public async Task returns_failure_status_when_conditionally_appending_to_a_deleted_stream() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - - await _fixture.Client.TombstoneAsync(stream, StreamState.Any); - - var result = await _fixture.Client.ConditionalAppendToStreamAsync( - stream, - StreamState.Any, - _fixture.CreateTestEvents() - ); - - Assert.Equal(ConditionalWriteResult.StreamDeleted, result); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/append_to_stream_expected_version_no_stream.cs b/test/EventStore.Client.Streams.Tests/append_to_stream_expected_version_no_stream.cs deleted file mode 100644 index 25590ebd1..000000000 --- a/test/EventStore.Client.Streams.Tests/append_to_stream_expected_version_no_stream.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -public class append_to_stream_expected_version_no_stream : IClassFixture { - readonly Fixture _fixture; - - public append_to_stream_expected_version_no_stream(Fixture fixture) => _fixture = fixture; - - [Fact] - public void succeeds() => Assert.Equal(new(0), _fixture.Result!.NextExpectedStreamRevision); - - [Fact] - public void returns_position() => Assert.True(_fixture.Result!.LogPosition > Position.Start); - - public class Fixture : EventStoreClientFixture { - public IWriteResult? Result { get; private set; } - - protected override Task Given() => Task.CompletedTask; - - protected override async Task When() => - Result = await Client.AppendToStreamAsync( - "stream-1", - StreamState.NoStream, - CreateTestEvents() - ); - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/append_to_stream_limits.cs b/test/EventStore.Client.Streams.Tests/append_to_stream_limits.cs deleted file mode 100644 index 27b0885a0..000000000 --- a/test/EventStore.Client.Streams.Tests/append_to_stream_limits.cs +++ /dev/null @@ -1,57 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -public class append_to_stream_limits : IClassFixture { - const int MaxAppendSize = 1024; - - readonly Fixture _fixture; - - public append_to_stream_limits(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task succeeds_when_size_is_less_than_max_append_size() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - _fixture.GetEvents(MaxAppendSize - 1) - ); - } - - [Fact] - public async Task fails_when_size_exceeds_max_append_size() { - var stream = _fixture.GetStreamName(); - - var ex = await Assert.ThrowsAsync( - () => _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - _fixture.GetEvents(MaxAppendSize * 2) - ) - ); - - Assert.Equal((uint)MaxAppendSize, ex.MaxAppendSize); - } - - public class Fixture : EventStoreClientFixture { - public Fixture() : base( - env: new() { - ["EVENTSTORE_MAX_APPEND_SIZE"] = $"{MaxAppendSize}" - } - ) { } - - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - - public IEnumerable GetEvents(int maxSize) { - var size = 0; - foreach (var e in CreateTestEvents(int.MaxValue)) { - size += e.Data.Length; - if (size >= maxSize) - yield break; - - yield return e; - } - } - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/append_to_stream_retry.cs b/test/EventStore.Client.Streams.Tests/append_to_stream_retry.cs deleted file mode 100644 index fd2c8e70c..000000000 --- a/test/EventStore.Client.Streams.Tests/append_to_stream_retry.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Grpc.Core; -using Polly; - -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class append_to_stream_retry : IClassFixture { - readonly Fixture _fixture; - - public append_to_stream_retry(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task can_retry() { - var stream = _fixture.GetStreamName(); - - // can definitely write without throwing - var nextExpected = (await WriteAnEventAsync(StreamRevision.None)).NextExpectedStreamRevision; - Assert.Equal(new(0), nextExpected); - - _fixture.TestServer.Stop(); - - // writeTask cannot complete because ES is stopped - var ex = await Assert.ThrowsAnyAsync(() => WriteAnEventAsync(new(0))); - Assert.True( - ex is RpcException { - Status: { - StatusCode: StatusCode.Unavailable - } - } or DiscoveryException - ); - - await _fixture.TestServer.StartAsync().WithTimeout(); - - // write can be retried - var writeResult = await Policy - .Handle() - .WaitAndRetryAsync(5, _ => TimeSpan.FromSeconds(3)) - .ExecuteAsync(async () => await WriteAnEventAsync(new(0))); - - Assert.Equal(new(1), writeResult.NextExpectedStreamRevision); - - return; - - Task WriteAnEventAsync(StreamRevision expectedRevision) => - _fixture.Client.AppendToStreamAsync( - stream, - expectedRevision, - _fixture.CreateTestEvents(1) - ); - } - - public class Fixture : EventStoreClientFixture { - public Fixture() : base( - env: new() { - ["EVENTSTORE_MEM_DB"] = "false" - } - ) => - Settings.ConnectivitySettings.MaxDiscoverAttempts = 2; - - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/append_to_stream_when_events_enumerator_throws.cs b/test/EventStore.Client.Streams.Tests/append_to_stream_when_events_enumerator_throws.cs deleted file mode 100644 index 70844f57e..000000000 --- a/test/EventStore.Client.Streams.Tests/append_to_stream_when_events_enumerator_throws.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -public class append_to_stream_when_events_enumerator_throws - : IClassFixture { - readonly Fixture _fixture; - - public append_to_stream_when_events_enumerator_throws(Fixture fixture) => _fixture = fixture; - - [Fact] - public void throws_the_exception() => Assert.IsType(_fixture.CaughtException); - - [Fact] - public async Task the_write_does_not_succeed() { - var result = _fixture.Client.ReadStreamAsync(Direction.Forwards, _fixture.StreamName, StreamPosition.Start); - Assert.Equal(ReadState.StreamNotFound, await result.ReadState); - } - - class EnumerationFailedException : Exception { } - - public class Fixture : EventStoreClientFixture { - public Fixture() => StreamName = GetStreamName("stream"); - - public string StreamName { get; } - public Exception? CaughtException { get; private set; } - - protected override async Task Given() { - try { - await Client.AppendToStreamAsync(StreamName, StreamRevision.None, Events()); - } - catch (Exception ex) { - CaughtException = ex; - } - - IEnumerable Events() { - var i = 0; - foreach (var e in CreateTestEvents(5)) { - if (i++ % 3 == 0) - throw new EnumerationFailedException(); - - yield return e; - } - } - } - - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/append_to_stream_with_timeout.cs b/test/EventStore.Client.Streams.Tests/append_to_stream_with_timeout.cs deleted file mode 100644 index 11189a163..000000000 --- a/test/EventStore.Client.Streams.Tests/append_to_stream_with_timeout.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Grpc.Core; - -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class append_to_stream_with_timeout : IClassFixture { - readonly Fixture _fixture; - - public append_to_stream_with_timeout(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task any_stream_revision_fails_when_operation_expired() { - var stream = _fixture.GetStreamName(); - - var ex = await Assert.ThrowsAsync( - () => - _fixture.Client.AppendToStreamAsync( - stream, - StreamState.Any, - _fixture.CreateTestEvents(100), - deadline: TimeSpan.FromTicks(1) - ) - ); - - Assert.Equal(StatusCode.DeadlineExceeded, ex.StatusCode); - } - - [Fact] - public async Task stream_revision_fails_when_operation_expired() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, _fixture.CreateTestEvents()); - - var ex = await Assert.ThrowsAsync( - () => - _fixture.Client.AppendToStreamAsync( - stream, - new StreamRevision(0), - _fixture.CreateTestEvents(100), - deadline: TimeSpan.Zero - ) - ); - - Assert.Equal(StatusCode.DeadlineExceeded, ex.StatusCode); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/appending_to_implicitly_created_stream.cs b/test/EventStore.Client.Streams.Tests/appending_to_implicitly_created_stream.cs deleted file mode 100644 index 99081b0a5..000000000 --- a/test/EventStore.Client.Streams.Tests/appending_to_implicitly_created_stream.cs +++ /dev/null @@ -1,275 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "LongRunning")] -public class appending_to_implicitly_created_stream - : IClassFixture { - readonly Fixture _fixture; - - public appending_to_implicitly_created_stream(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0em1_idempotent() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(6).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(1)); - - var count = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - - Assert.Equal(events.Length, count); - } - - [Fact] - public async Task sequence_0em1_1e0_2e1_3e2_4e3_4e4_0any_idempotent() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(6).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, events.Take(1)); - - var count = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - - Assert.Equal(events.Length, count); - } - - [Fact] - public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e5_non_idempotent() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(6).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - await _fixture.Client.AppendToStreamAsync(stream, new StreamRevision(5), events.Take(1)); - - var count = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 2).CountAsync(); - - Assert.Equal(events.Length + 1, count); - } - - [Fact] - public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e6_throws_wev() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(6).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - await Assert.ThrowsAsync(() => _fixture.Client.AppendToStreamAsync(stream, new StreamRevision(6), events.Take(1))); - } - - [Fact] - public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e6_returns_wev() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(6).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - var writeResult = await _fixture.Client.AppendToStreamAsync( - stream, - new StreamRevision(6), - events.Take(1), - options => options.ThrowOnAppendFailure = false - ); - - Assert.IsType(writeResult); - } - - [Fact] - public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e4_throws_wev() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(6).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - await Assert.ThrowsAsync(() => _fixture.Client.AppendToStreamAsync(stream, new StreamRevision(4), events.Take(1))); - } - - [Fact] - public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e4_returns_wev() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(6).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - var writeResult = await _fixture.Client.AppendToStreamAsync( - stream, - new StreamRevision(4), - events.Take(1), - options => options.ThrowOnAppendFailure = false - ); - - Assert.IsType(writeResult); - } - - [Fact] - public async Task sequence_0em1_0e0_non_idempotent() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents().ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - await _fixture.Client.AppendToStreamAsync(stream, new StreamRevision(0), events.Take(1)); - - var count = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 2).CountAsync(); - - Assert.Equal(events.Length + 1, count); - } - - [Fact] - public async Task sequence_0em1_0any_idempotent() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents().ToArray(); - - await Task.Delay(TimeSpan.FromSeconds(30)); - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, events.Take(1)); - - var count = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - - Assert.Equal(events.Length, count); - } - - [Fact] - public async Task sequence_0em1_0em1_idempotent() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents().ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(1)); - - var count = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - - Assert.Equal(events.Length, count); - } - - [Fact] - public async Task sequence_0em1_1e0_2e1_1any_1any_idempotent() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(3).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, events.Skip(1).Take(1)); - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, events.Skip(1).Take(1)); - - var count = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - - Assert.Equal(events.Length, count); - } - - [Fact] - public async Task sequence_S_0em1_1em1_E_S_0em1_E_idempotent() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(2).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(1)); - - var count = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - - Assert.Equal(events.Length, count); - } - - [Fact] - public async Task sequence_S_0em1_1em1_E_S_0any_E_idempotent() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(2).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, events.Take(1)); - - var count = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - - Assert.Equal(events.Length, count); - } - - [Fact] - public async Task sequence_S_0em1_1em1_E_S_1e0_E_idempotent() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(2).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - await _fixture.Client.AppendToStreamAsync(stream, new StreamRevision(0), events.Skip(1)); - - var count = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - - Assert.Equal(events.Length, count); - } - - [Fact] - public async Task sequence_S_0em1_1em1_E_S_1any_E_idempotent() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(2).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, events.Skip(1).Take(1)); - - var count = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - - Assert.Equal(events.Length, count); - } - - [Fact] - public async Task sequence_S_0em1_1em1_E_S_0em1_1em1_2em1_E_idempotancy_fail_throws() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(3).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(2)); - - await Assert.ThrowsAsync( - () => _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - events - ) - ); - } - - [Fact] - public async Task sequence_S_0em1_1em1_E_S_0em1_1em1_2em1_E_idempotancy_fail_returns() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(3).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(2)); - - var writeResult = await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - events, - options => options.ThrowOnAppendFailure = false - ); - - Assert.IsType(writeResult); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/delete_stream_with_timeout.cs b/test/EventStore.Client.Streams.Tests/delete_stream_with_timeout.cs deleted file mode 100644 index 6028153df..000000000 --- a/test/EventStore.Client.Streams.Tests/delete_stream_with_timeout.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Grpc.Core; - -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class deleting_stream_with_timeout : IClassFixture { - readonly Fixture _fixture; - - public deleting_stream_with_timeout(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task any_stream_revision_delete_fails_when_operation_expired() { - var stream = _fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync( - () => - _fixture.Client.DeleteAsync(stream, StreamState.Any, TimeSpan.Zero) - ); - - Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); - } - - [Fact] - public async Task stream_revision_delete_fails_when_operation_expired() { - var stream = _fixture.GetStreamName(); - - var rpcException = await Assert.ThrowsAsync( - () => - _fixture.Client.DeleteAsync(stream, new StreamRevision(0), TimeSpan.Zero) - ); - - Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); - } - - [Fact] - public async Task any_stream_revision_tombstoning_fails_when_operation_expired() { - var stream = _fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync( - () => - _fixture.Client.TombstoneAsync(stream, StreamState.Any, TimeSpan.Zero) - ); - - Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); - } - - [Fact] - public async Task stream_revision_tombstoning_fails_when_operation_expired() { - var stream = _fixture.GetStreamName(); - - var rpcException = await Assert.ThrowsAsync( - () => - _fixture.Client.TombstoneAsync(stream, new StreamRevision(0), TimeSpan.Zero) - ); - - Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/deleting_stream.cs b/test/EventStore.Client.Streams.Tests/deleting_stream.cs deleted file mode 100644 index 98a7799e1..000000000 --- a/test/EventStore.Client.Streams.Tests/deleting_stream.cs +++ /dev/null @@ -1,88 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class deleting_stream : IClassFixture { - readonly Fixture _fixture; - - public deleting_stream(Fixture fixture) => _fixture = fixture; - - public static IEnumerable ExpectedStreamStateCases() { - yield return new object?[] { StreamState.Any, nameof(StreamState.Any) }; - yield return new object?[] { StreamState.NoStream, nameof(StreamState.NoStream) }; - } - - [Theory] - [MemberData(nameof(ExpectedStreamStateCases))] - public async Task hard_deleting_a_stream_that_does_not_exist_with_expected_version_does_not_throw(StreamState expectedVersion, string name) { - var stream = $"{_fixture.GetStreamName()}_{name}"; - - await _fixture.Client.TombstoneAsync(stream, expectedVersion); - } - - [Regression.Fact(21, "fixed by")] - public async Task soft_deleting_a_stream_that_exists() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamRevision.None, _fixture.CreateTestEvents()); - - await _fixture.Client.DeleteAsync(stream, StreamState.StreamExists); - } - - [Fact] - public async Task hard_deleting_a_stream_that_does_not_exist_with_wrong_expected_version_throws() { - var stream = _fixture.GetStreamName(); - - await Assert.ThrowsAsync(() => _fixture.Client.TombstoneAsync(stream, new StreamRevision(0))); - } - - [Fact] - public async Task soft_deleting_a_stream_that_does_not_exist_with_wrong_expected_version_throws() { - var stream = _fixture.GetStreamName(); - - await Assert.ThrowsAsync(() => _fixture.Client.DeleteAsync(stream, new StreamRevision(0))); - } - - [Fact] - public async Task hard_deleting_a_stream_should_return_log_position() { - var stream = _fixture.GetStreamName(); - - var writeResult = await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - _fixture.CreateTestEvents() - ); - - var deleteResult = await _fixture.Client.TombstoneAsync(stream, writeResult.NextExpectedStreamRevision); - - Assert.True(deleteResult.LogPosition > writeResult.LogPosition); - } - - [Fact] - public async Task soft_deleting_a_stream_should_return_log_position() { - var stream = _fixture.GetStreamName(); - - var writeResult = await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - _fixture.CreateTestEvents() - ); - - var deleteResult = await _fixture.Client.DeleteAsync(stream, writeResult.NextExpectedStreamRevision); - - Assert.True(deleteResult.LogPosition > writeResult.LogPosition); - } - - [Fact] - public async Task hard_deleting_a_deleted_stream_should_throw() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.TombstoneAsync(stream, StreamState.NoStream); - - await Assert.ThrowsAsync(() => _fixture.Client.TombstoneAsync(stream, StreamState.NoStream)); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_all_backward_messages.cs b/test/EventStore.Client.Streams.Tests/read_all_backward_messages.cs deleted file mode 100644 index 1c980605a..000000000 --- a/test/EventStore.Client.Streams.Tests/read_all_backward_messages.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class read_all_backward_messages : IClassFixture { - readonly Fixture _fixture; - - public read_all_backward_messages(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task stream_found() { - var events = _fixture.CreateTestEvents(32).ToArray(); - - var streamName = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync(streamName, StreamState.NoStream, events); - - var result = await _fixture.Client.ReadAllAsync( - Direction.Backwards, - Position.End, - 32, - userCredentials: TestCredentials.Root - ).Messages.ToArrayAsync(); - - Assert.Equal(32, result.OfType().Count()); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_all_events_backward.cs b/test/EventStore.Client.Streams.Tests/read_all_events_backward.cs deleted file mode 100644 index bd571b281..000000000 --- a/test/EventStore.Client.Streams.Tests/read_all_events_backward.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "LongRunning")] -public class read_all_events_backward : IClassFixture { - const string Stream = "stream"; - readonly Fixture _fixture; - - public read_all_events_backward(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task return_empty_if_reading_from_start() { - var count = await _fixture.Client.ReadAllAsync(Direction.Backwards, Position.Start, 1).CountAsync(); - Assert.Equal(0, count); - } - - [Fact] - public async Task return_partial_slice_if_not_enough_events() { - var events = await _fixture.Client.ReadAllAsync(Direction.Backwards, Position.End, _fixture.Events.Length * 2) - .ToArrayAsync(); - - Assert.True(events.Length < _fixture.Events.Length * 2); - } - - [Fact] - public async Task return_events_in_reversed_order_compared_to_written() { - var events = await _fixture.Client.ReadAllAsync(Direction.Backwards, Position.End, _fixture.Events.Length) - .ToArrayAsync(); - - Assert.True( - EventDataComparer.Equal( - _fixture.Events.Reverse().ToArray(), - events.AsResolvedTestEvents().ToArray() - ) - ); - } - - [Fact] - public async Task return_single_event() { - var events = await _fixture.Client.ReadAllAsync(Direction.Backwards, Position.End, 1) - .ToArrayAsync(); - - var actualEvent = Assert.Single(events.AsResolvedTestEvents()); - Assert.True( - EventDataComparer.Equal( - _fixture.Events.Last(), - actualEvent - ) - ); - } - - [Fact(Skip = "Not Implemented")] - public Task be_able_to_read_all_one_by_one_until_end_of_stream() => throw new NotImplementedException(); - - [Fact(Skip = "Not Implemented")] - public Task be_able_to_read_events_slice_at_time() => throw new NotImplementedException(); - - [Fact(Skip = "Not Implemented")] - public Task when_got_int_max_value_as_maxcount_should_throw() => throw new NotImplementedException(); - - [Fact] - public async Task max_count_is_respected() { - var maxCount = _fixture.Events.Length / 2; - var events = await _fixture.Client.ReadAllAsync(Direction.Backwards, Position.End, maxCount) - .Take(_fixture.Events.Length) - .ToArrayAsync(); - - Assert.Equal(maxCount, events.Length); - } - - public class Fixture : EventStoreClientFixture { - public Fixture() => - Events = Enumerable - .Concat( - CreateTestEvents(20), - CreateTestEvents(2, metadataSize: 1_000_000) - ) - .ToArray(); - - public EventData[] Events { get; } - - protected override async Task Given() { - var result = await Client.SetStreamMetadataAsync( - SystemStreams.AllStream, - StreamState.NoStream, - new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root - ); - - await Client.AppendToStreamAsync(Stream, StreamState.NoStream, Events); - } - - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_all_events_forward.cs b/test/EventStore.Client.Streams.Tests/read_all_events_forward.cs deleted file mode 100644 index 335d3e77e..000000000 --- a/test/EventStore.Client.Streams.Tests/read_all_events_forward.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "LongRunning")] -public class read_all_events_forward : IClassFixture { - const string Stream = "stream"; - readonly Fixture _fixture; - - public read_all_events_forward(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task return_empty_if_reading_from_end() { - var count = await _fixture.Client.ReadAllAsync(Direction.Forwards, Position.End, 1).CountAsync(); - Assert.Equal(0, count); - } - - [Fact] - public async Task return_partial_slice_if_not_enough_events() { - var events = await _fixture.Client - .ReadAllAsync(Direction.Forwards, Position.Start, _fixture.Events.Length * 2) - .ToArrayAsync(); - - Assert.True(events.Length < _fixture.Events.Length * 2); - } - - [Fact] - public async Task return_events_in_correct_order_compared_to_written() { - var events = await _fixture.Client - .ReadAllAsync(Direction.Forwards, Position.Start, _fixture.Events.Length * 2) - .ToArrayAsync(); - - Assert.True(EventDataComparer.Equal(_fixture.Events, events.AsResolvedTestEvents().ToArray())); - } - - [Fact] - public async Task return_single_event() { - var events = await _fixture.Client.ReadAllAsync(Direction.Forwards, Position.Start, 1) - .ToArrayAsync(); - - Assert.Single(events); - } - - [Fact(Skip = "Not Implemented")] - public Task be_able_to_read_all_one_by_one_until_end_of_stream() => throw new NotImplementedException(); - - [Fact(Skip = "Not Implemented")] - public Task be_able_to_read_events_slice_at_time() => throw new NotImplementedException(); - - [Fact(Skip = "Not Implemented")] - public Task when_got_int_max_value_as_maxcount_should_throw() => throw new NotImplementedException(); - - [Fact] - public async Task max_count_is_respected() { - var maxCount = _fixture.Events.Length / 2; - var events = await _fixture.Client.ReadAllAsync(Direction.Forwards, Position.Start, maxCount) - .Take(_fixture.Events.Length) - .ToArrayAsync(); - - Assert.Equal(maxCount, events.Length); - } - - [Fact] - public async Task reads_all_events_by_default() { - var count = await _fixture.Client.ReadAllAsync(Direction.Forwards, Position.Start) - .CountAsync(); - - Assert.True(count >= _fixture.Events.Length); - } - - public class Fixture : EventStoreClientFixture { - public Fixture() => - Events = Enumerable - .Concat( - CreateTestEvents(20), - CreateTestEvents(2, metadataSize: 1_000_000) - ) - .ToArray(); - - public EventData[] Events { get; } - - protected override async Task Given() { - var result = await Client.SetStreamMetadataAsync( - SystemStreams.AllStream, - StreamState.NoStream, - new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root - ); - - await Client.AppendToStreamAsync(Stream, StreamState.NoStream, Events); - } - - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_all_events_forward_with_linkto_passed_max_count.cs b/test/EventStore.Client.Streams.Tests/read_all_events_forward_with_linkto_passed_max_count.cs deleted file mode 100644 index 50ff40b1e..000000000 --- a/test/EventStore.Client.Streams.Tests/read_all_events_forward_with_linkto_passed_max_count.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Text; - -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "LongRunning")] -public class read_all_events_forward_with_linkto_passed_max_count - : IClassFixture { - readonly Fixture _fixture; - - public read_all_events_forward_with_linkto_passed_max_count(Fixture fixture) => _fixture = fixture; - - [Fact] - public void one_event_is_read() => Assert.Single(_fixture.Events ?? Array.Empty()); - - public class Fixture : EventStoreClientFixture { - const string DeletedStream = nameof(DeletedStream); - const string LinkedStream = nameof(LinkedStream); - public ResolvedEvent[]? Events { get; private set; } - - protected override async Task Given() { - await Client.AppendToStreamAsync(DeletedStream, StreamState.Any, CreateTestEvents()); - await Client.SetStreamMetadataAsync( - DeletedStream, - StreamState.Any, - new(2) - ); - - await Client.AppendToStreamAsync(DeletedStream, StreamState.Any, CreateTestEvents()); - await Client.AppendToStreamAsync(DeletedStream, StreamState.Any, CreateTestEvents()); - await Client.AppendToStreamAsync( - LinkedStream, - StreamState.Any, - new[] { - new EventData( - Uuid.NewUuid(), - SystemEventTypes.LinkTo, - Encoding.UTF8.GetBytes("0@" + DeletedStream), - Array.Empty(), - Constants.Metadata.ContentTypes.ApplicationOctetStream - ) - } - ); - } - - protected override async Task When() => - Events = await Client.ReadStreamAsync( - Direction.Forwards, - LinkedStream, - StreamPosition.Start, - resolveLinkTos: true - ) - .ToArrayAsync(); - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_all_forward_messages.cs b/test/EventStore.Client.Streams.Tests/read_all_forward_messages.cs deleted file mode 100644 index 34ffbffd7..000000000 --- a/test/EventStore.Client.Streams.Tests/read_all_forward_messages.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class read_all_forward_messages : IClassFixture { - readonly Fixture _fixture; - - public read_all_forward_messages(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task stream_found() { - var events = _fixture.CreateTestEvents(32).ToArray(); - - var streamName = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync(streamName, StreamState.NoStream, events); - - var result = await _fixture.Client.ReadAllAsync( - Direction.Forwards, - Position.Start, - 32, - userCredentials: TestCredentials.Root - ).Messages.ToArrayAsync(); - - Assert.Equal(32, result.OfType().Count()); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_all_with_timeout.cs b/test/EventStore.Client.Streams.Tests/read_all_with_timeout.cs deleted file mode 100644 index ffbf644c3..000000000 --- a/test/EventStore.Client.Streams.Tests/read_all_with_timeout.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Grpc.Core; - -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class read_all_with_timeout : IClassFixture { - readonly Fixture _fixture; - - public read_all_with_timeout(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task fails_when_operation_expired() { - var rpcException = await Assert.ThrowsAsync( - () => _fixture.Client - .ReadAllAsync( - Direction.Backwards, - Position.Start, - 1, - false, - TimeSpan.Zero - ) - .ToArrayAsync().AsTask() - ); - - Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_enumeration_tests.cs b/test/EventStore.Client.Streams.Tests/read_enumeration_tests.cs deleted file mode 100644 index 9e9f22847..000000000 --- a/test/EventStore.Client.Streams.Tests/read_enumeration_tests.cs +++ /dev/null @@ -1,75 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class read_enumeration_tests : IClassFixture { - readonly Fixture _fixture; - - public read_enumeration_tests(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task all_referencing_messages_twice_does_not_throw() { - var result = _fixture.Client.ReadAllAsync( - Direction.Forwards, - Position.Start, - 32, - userCredentials: TestCredentials.Root - ); - - _ = result.Messages; - await result.Messages.ToArrayAsync(); - } - - [Fact] - public async Task all_enumerating_messages_twice_throws() { - var result = _fixture.Client.ReadAllAsync( - Direction.Forwards, - Position.Start, - 32, - userCredentials: TestCredentials.Root - ); - - await result.Messages.ToArrayAsync(); - - await Assert.ThrowsAsync( - async () => - await result.Messages.ToArrayAsync() - ); - } - - [Fact] - public async Task referencing_messages_twice_does_not_throw() { - var result = _fixture.Client.ReadStreamAsync( - Direction.Forwards, - "$users", - StreamPosition.Start, - 32, - userCredentials: TestCredentials.Root - ); - - _ = result.Messages; - await result.Messages.ToArrayAsync(); - } - - [Fact] - public async Task enumerating_messages_twice_throws() { - var result = _fixture.Client.ReadStreamAsync( - Direction.Forwards, - "$users", - StreamPosition.Start, - 32, - userCredentials: TestCredentials.Root - ); - - await result.Messages.ToArrayAsync(); - - await Assert.ThrowsAsync( - async () => - await result.Messages.ToArrayAsync() - ); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_events_linked_to_deleted_stream.cs b/test/EventStore.Client.Streams.Tests/read_events_linked_to_deleted_stream.cs deleted file mode 100644 index 2fded6228..000000000 --- a/test/EventStore.Client.Streams.Tests/read_events_linked_to_deleted_stream.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Text; - -namespace EventStore.Client.Streams.Tests; - -public abstract class read_events_linked_to_deleted_stream { - readonly Fixture _fixture; - - protected read_events_linked_to_deleted_stream(Fixture fixture) => _fixture = fixture; - - [Fact] - public void one_event_is_read() => Assert.Single(_fixture.Events ?? Array.Empty()); - - [Fact] - public void the_linked_event_is_not_resolved() => Assert.Null(_fixture.Events![0].Event); - - [Fact] - public void the_link_event_is_included() => Assert.NotNull(_fixture.Events![0].OriginalEvent); - - [Fact] - public void the_event_is_not_resolved() => Assert.False(_fixture.Events![0].IsResolved); - - public abstract class Fixture : EventStoreClientFixture { - const string DeletedStream = nameof(DeletedStream); - protected const string LinkedStream = nameof(LinkedStream); - public ResolvedEvent[]? Events { get; private set; } - - protected override async Task Given() { - await Client.AppendToStreamAsync(DeletedStream, StreamState.Any, CreateTestEvents()); - await Client.AppendToStreamAsync( - LinkedStream, - StreamState.Any, - new[] { - new EventData( - Uuid.NewUuid(), - SystemEventTypes.LinkTo, - Encoding.UTF8.GetBytes("0@" + DeletedStream), - Array.Empty(), - Constants.Metadata.ContentTypes.ApplicationOctetStream - ) - } - ); - - await Client.DeleteAsync(DeletedStream, StreamState.Any); - } - - protected override async Task When() => Events = await Read(); - - protected abstract ValueTask Read(); - } - - public class @forwards : read_events_linked_to_deleted_stream, IClassFixture { - public forwards(Fixture fixture) : base(fixture) { } - - public new class Fixture : read_events_linked_to_deleted_stream.Fixture { - protected override ValueTask Read() => - Client.ReadStreamAsync( - Direction.Forwards, - LinkedStream, - StreamPosition.Start, - 1, - true - ).ToArrayAsync(); - } - } - - public class @backwards : read_events_linked_to_deleted_stream, IClassFixture { - public backwards(Fixture fixture) : base(fixture) { } - - public new class Fixture : read_events_linked_to_deleted_stream.Fixture { - protected override ValueTask Read() => - Client.ReadStreamAsync( - Direction.Backwards, - LinkedStream, - StreamPosition.Start, - 1, - true - ).ToArrayAsync(); - } - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_stream_backward.cs b/test/EventStore.Client.Streams.Tests/read_stream_backward.cs deleted file mode 100644 index 56277881b..000000000 --- a/test/EventStore.Client.Streams.Tests/read_stream_backward.cs +++ /dev/null @@ -1,191 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class read_stream_backward : IClassFixture { - readonly Fixture _fixture; - - public read_stream_backward(Fixture fixture) => _fixture = fixture; - - [Theory] - [InlineData(0)] - public async Task count_le_equal_zero_throws(long maxCount) { - var stream = _fixture.GetStreamName(); - - var ex = await Assert.ThrowsAsync( - () => - _fixture.Client.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.Start, maxCount) - .ToArrayAsync().AsTask() - ); - - Assert.Equal(nameof(maxCount), ex.ParamName); - } - - [Fact] - public async Task stream_does_not_exist_throws() { - var stream = _fixture.GetStreamName(); - - var ex = await Assert.ThrowsAsync( - () => _fixture.Client - .ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) - .ToArrayAsync().AsTask() - ); - - Assert.Equal(stream, ex.Stream); - } - - [Fact] - public async Task stream_does_not_exist_can_be_checked() { - var stream = _fixture.GetStreamName(); - - var result = _fixture.Client.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1); - - var state = await result.ReadState; - Assert.Equal(ReadState.StreamNotFound, state); - } - - [Fact] - public async Task stream_deleted_throws() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.TombstoneAsync(stream, StreamState.NoStream); - - var ex = await Assert.ThrowsAsync( - () => _fixture.Client - .ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) - .ToArrayAsync().AsTask() - ); - - Assert.Equal(stream, ex.Stream); - } - - [Theory] - [InlineData("small_events", 10, 1)] - [InlineData("large_events", 2, 1_000_000)] - public async Task returns_events_in_reversed_order(string suffix, int count, int metadataSize) { - var stream = $"{_fixture.GetStreamName()}_{suffix}"; - - var expected = _fixture.CreateTestEvents(count, metadataSize: metadataSize).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, expected); - - var actual = await _fixture.Client - .ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, expected.Length) - .Select(x => x.Event).ToArrayAsync(); - - Assert.True( - EventDataComparer.Equal( - expected.Reverse().ToArray(), - actual - ) - ); - } - - [Fact] - public async Task be_able_to_read_single_event_from_arbitrary_position() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(10).ToArray(); - - var expected = events[7]; - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - var actual = await _fixture.Client.ReadStreamAsync(Direction.Backwards, stream, new(7), 1) - .Select(x => x.Event) - .SingleAsync(); - - Assert.True(EventDataComparer.Equal(expected, actual)); - } - - [Fact] - public async Task be_able_to_read_from_arbitrary_position() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(10).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - var actual = await _fixture.Client.ReadStreamAsync(Direction.Backwards, stream, new(3), 2) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.True(EventDataComparer.Equal(events.Skip(2).Take(2).Reverse().ToArray(), actual)); - } - - [Fact] - public async Task be_able_to_read_first_event() { - var stream = _fixture.GetStreamName(); - - var testEvents = _fixture.CreateTestEvents(10).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); - - var events = await _fixture.Client.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.Start, 1) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.Single(events); - Assert.True(EventDataComparer.Equal(testEvents[0], events[0])); - } - - [Fact] - public async Task be_able_to_read_last_event() { - var stream = _fixture.GetStreamName(); - - var testEvents = _fixture.CreateTestEvents(10).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); - - var events = await _fixture.Client.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.Single(events); - Assert.True(EventDataComparer.Equal(testEvents[^1], events[0])); - } - - [Fact] - public async Task max_count_is_respected() { - var streamName = _fixture.GetStreamName(); - const int count = 20; - const long maxCount = count / 2; - - await _fixture.Client.AppendToStreamAsync( - streamName, - StreamState.NoStream, - _fixture.CreateTestEvents(count) - ); - - var events = await _fixture.Client - .ReadStreamAsync(Direction.Backwards, streamName, StreamPosition.End, maxCount) - .Take(count) - .ToArrayAsync(); - - Assert.Equal(maxCount, events.Length); - } - - [Fact] - public async Task populates_log_position_of_event() { - if (EventStoreTestServer.Version.Major < 22) - return; - - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(1).ToArray(); - - var writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - var actual = await _fixture.Client.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.Single(actual); - Assert.Equal(writeResult.LogPosition.PreparePosition, writeResult.LogPosition.CommitPosition); - Assert.Equal(writeResult.LogPosition, actual.First().Position); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_stream_backward_messages.cs b/test/EventStore.Client.Streams.Tests/read_stream_backward_messages.cs deleted file mode 100644 index 581194679..000000000 --- a/test/EventStore.Client.Streams.Tests/read_stream_backward_messages.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class read_stream_backward_messages : IClassFixture { - readonly Fixture _fixture; - - public read_stream_backward_messages(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task stream_not_found() { - var result = await _fixture.Client.ReadStreamAsync( - Direction.Backwards, - _fixture.GetStreamName(), - StreamPosition.End - ).Messages.SingleAsync(); - - Assert.Equal(StreamMessage.NotFound.Instance, result); - } - - [Fact] - public async Task stream_found() { - var events = _fixture.CreateTestEvents(_fixture.Count).ToArray(); - - var streamName = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync(streamName, StreamState.NoStream, events); - - var result = await _fixture.Client.ReadStreamAsync( - Direction.Backwards, - streamName, - StreamPosition.End - ).Messages.ToArrayAsync(); - - Assert.Equal( - _fixture.Count + (_fixture.HasLastStreamPosition ? 2 : 1), - result.Length - ); - - Assert.Equal(StreamMessage.Ok.Instance, result[0]); - Assert.Equal(_fixture.Count, result.OfType().Count()); - if (_fixture.HasLastStreamPosition) - Assert.Equal(new StreamMessage.LastStreamPosition(new(31)), result[^1]); - } - - public class Fixture : EventStoreClientFixture { - public int Count => 32; - public bool HasLastStreamPosition => (EventStoreTestServer.Version?.Major ?? int.MaxValue) >= 21; - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_stream_forward.cs b/test/EventStore.Client.Streams.Tests/read_stream_forward.cs deleted file mode 100644 index a48a94a2e..000000000 --- a/test/EventStore.Client.Streams.Tests/read_stream_forward.cs +++ /dev/null @@ -1,186 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class read_stream_forward : IClassFixture { - readonly Fixture _fixture; - - public read_stream_forward(Fixture fixture) => _fixture = fixture; - - [Theory] - [InlineData(0)] - public async Task count_le_equal_zero_throws(long maxCount) { - var stream = _fixture.GetStreamName(); - - var ex = await Assert.ThrowsAsync( - () => - _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, maxCount) - .ToArrayAsync().AsTask() - ); - - Assert.Equal(nameof(maxCount), ex.ParamName); - } - - [Fact] - public async Task stream_does_not_exist_throws() { - var stream = _fixture.GetStreamName(); - - var ex = await Assert.ThrowsAsync( - () => _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) - .ToArrayAsync().AsTask() - ); - - Assert.Equal(stream, ex.Stream); - } - - [Fact] - public async Task stream_does_not_exist_can_be_checked() { - var stream = _fixture.GetStreamName(); - - var result = _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1); - - var state = await result.ReadState; - Assert.Equal(ReadState.StreamNotFound, state); - } - - [Fact] - public async Task stream_deleted_throws() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.TombstoneAsync(stream, StreamState.NoStream); - - var ex = await Assert.ThrowsAsync( - () => _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) - .ToArrayAsync().AsTask() - ); - - Assert.Equal(stream, ex.Stream); - } - - [Theory] - [InlineData("small_events", 10, 1)] - [InlineData("large_events", 2, 1_000_000)] - public async Task returns_events_in_order(string suffix, int count, int metadataSize) { - var stream = $"{_fixture.GetStreamName()}_{suffix}"; - - var expected = _fixture.CreateTestEvents(count, metadataSize: metadataSize).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, expected); - - var actual = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, expected.Length) - .Select(x => x.Event).ToArrayAsync(); - - Assert.True(EventDataComparer.Equal(expected, actual)); - } - - [Fact] - public async Task be_able_to_read_single_event_from_arbitrary_position() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(10).ToArray(); - - var expected = events[7]; - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - var actual = await _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, new(7), 1) - .Select(x => x.Event) - .SingleAsync(); - - Assert.True(EventDataComparer.Equal(expected, actual)); - } - - [Fact] - public async Task be_able_to_read_from_arbitrary_position() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(10).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - var actual = await _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, new(3), 2) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.True(EventDataComparer.Equal(events.Skip(3).Take(2).ToArray(), actual)); - } - - [Fact] - public async Task be_able_to_read_first_event() { - var stream = _fixture.GetStreamName(); - - var testEvents = _fixture.CreateTestEvents(10).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); - - var events = await _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.Single(events); - Assert.True(EventDataComparer.Equal(testEvents[0], events[0])); - } - - [Fact] - public async Task max_count_is_respected() { - var streamName = _fixture.GetStreamName(); - const int count = 20; - const long maxCount = count / 2; - - await _fixture.Client.AppendToStreamAsync( - streamName, - StreamState.NoStream, - _fixture.CreateTestEvents(count) - ); - - var events = await _fixture.Client.ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start, maxCount) - .Take(count) - .ToArrayAsync(); - - Assert.Equal(maxCount, events.Length); - } - - [Fact] - public async Task reads_all_events_by_default() { - var streamName = _fixture.GetStreamName(); - const int maxCount = 200; - await _fixture.Client.AppendToStreamAsync( - streamName, - StreamState.NoStream, - _fixture.CreateTestEvents(maxCount) - ); - - var count = await _fixture.Client - .ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start) - .CountAsync(); - - Assert.True(count == maxCount); - } - - [Fact] - public async Task populates_log_position_of_event() { - if (EventStoreTestServer.Version.Major < 22) - return; - - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(1).ToArray(); - - var writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, events); - - var actual = await _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.Single(actual); - Assert.Equal(writeResult.LogPosition.PreparePosition, writeResult.LogPosition.CommitPosition); - Assert.Equal(writeResult.LogPosition, actual.First().Position); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_stream_forward_messages.cs b/test/EventStore.Client.Streams.Tests/read_stream_forward_messages.cs deleted file mode 100644 index 69d0d1538..000000000 --- a/test/EventStore.Client.Streams.Tests/read_stream_forward_messages.cs +++ /dev/null @@ -1,99 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class read_stream_forward_messages : IClassFixture { - readonly Fixture _fixture; - - public read_stream_forward_messages(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task stream_not_found() { - var result = await _fixture.Client.ReadStreamAsync( - Direction.Forwards, - _fixture.GetStreamName(), - StreamPosition.Start - ).Messages.SingleAsync(); - - Assert.Equal(StreamMessage.NotFound.Instance, result); - } - - [Fact] - public async Task stream_found() { - var events = _fixture.CreateTestEvents(_fixture.Count).ToArray(); - - var streamName = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync(streamName, StreamState.NoStream, events); - - var result = await _fixture.Client.ReadStreamAsync( - Direction.Forwards, - streamName, - StreamPosition.Start - ).Messages.ToArrayAsync(); - - Assert.Equal( - _fixture.Count + (_fixture.HasStreamPositions ? 2 : 1), - result.Length - ); - - Assert.Equal(StreamMessage.Ok.Instance, result[0]); - Assert.Equal(_fixture.Count, result.OfType().Count()); - var first = Assert.IsType(result[1]); - Assert.Equal(new(0), first.ResolvedEvent.OriginalEventNumber); - var last = Assert.IsType(result[_fixture.HasStreamPositions ? ^2 : ^1]); - Assert.Equal(new((ulong)_fixture.Count - 1), last.ResolvedEvent.OriginalEventNumber); - - if (_fixture.HasStreamPositions) - if (_fixture.HasStreamPositions) - Assert.Equal( - new StreamMessage.LastStreamPosition(new((ulong)_fixture.Count - 1)), - result[^1] - ); - } - - [Fact] - public async Task stream_found_truncated() { - var events = _fixture.CreateTestEvents(_fixture.Count).ToArray(); - - var streamName = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync(streamName, StreamState.NoStream, events); - - await _fixture.Client.SetStreamMetadataAsync( - streamName, - StreamState.Any, - new(truncateBefore: new StreamPosition(32)) - ); - - var result = await _fixture.Client.ReadStreamAsync( - Direction.Forwards, - streamName, - StreamPosition.Start - ).Messages.ToArrayAsync(); - - Assert.Equal( - _fixture.Count - 32 + (_fixture.HasStreamPositions ? 3 : 1), - result.Length - ); - - Assert.Equal(StreamMessage.Ok.Instance, result[0]); - - if (_fixture.HasStreamPositions) - Assert.Equal(new StreamMessage.FirstStreamPosition(new(32)), result[1]); - - Assert.Equal(32, result.OfType().Count()); - - if (_fixture.HasStreamPositions) - Assert.Equal( - new StreamMessage.LastStreamPosition(new((ulong)_fixture.Count - 1)), - result[^1] - ); - } - - public class Fixture : EventStoreClientFixture { - public int Count => 64; - public bool HasStreamPositions => (EventStoreTestServer.Version?.Major ?? int.MaxValue) >= 21; - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/read_stream_with_timeout.cs b/test/EventStore.Client.Streams.Tests/read_stream_with_timeout.cs deleted file mode 100644 index 2f1fbac98..000000000 --- a/test/EventStore.Client.Streams.Tests/read_stream_with_timeout.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Grpc.Core; - -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class read_stream_with_timeout : IClassFixture { - readonly Fixture _fixture; - - public read_stream_with_timeout(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task fails_when_operation_expired() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamRevision.None, _fixture.CreateTestEvents()); - - var rpcException = await Assert.ThrowsAsync( - () => _fixture.Client - .ReadStreamAsync( - Direction.Backwards, - stream, - StreamPosition.End, - 1, - false, - TimeSpan.Zero - ) - .ToArrayAsync().AsTask() - ); - - Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/reconnection.cs b/test/EventStore.Client.Streams.Tests/reconnection.cs deleted file mode 100644 index bdb2174a6..000000000 --- a/test/EventStore.Client.Streams.Tests/reconnection.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Grpc.Core; - -namespace EventStore.Client.Streams.Tests; - -public class @reconnection : IClassFixture { - readonly Fixture _fixture; - - public reconnection(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task when_the_connection_is_lost() { - var streamName = _fixture.GetStreamName(); - var eventCount = 512; - var receivedAllEvents = new TaskCompletionSource(); - var serverRestarted = new TaskCompletionSource(); - var receivedEvents = new List(); - var resubscribed = new TaskCompletionSource(); - - using var _ = await _fixture.Client.SubscribeToStreamAsync( - streamName, - FromStream.Start, - EventAppeared, - subscriptionDropped: SubscriptionDropped - ) - .WithTimeout(); - - await _fixture.Client - .AppendToStreamAsync(streamName, StreamState.NoStream, _fixture.CreateTestEvents(eventCount)) - .WithTimeout(); // ensure we get backpressure - - _fixture.TestServer.Stop(); - await Task.Delay(TimeSpan.FromSeconds(2)); - - await _fixture.TestServer.StartAsync().WithTimeout(); - serverRestarted.SetResult(); - - await resubscribed.Task.WithTimeout(TimeSpan.FromSeconds(10)); - - await receivedAllEvents.Task.WithTimeout(TimeSpan.FromSeconds(10)); - - async Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - await serverRestarted.Task; - receivedEvents.Add(e); - if (receivedEvents.Count == eventCount) - receivedAllEvents.TrySetResult(); - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) { - if (reason == SubscriptionDroppedReason.Disposed || ex is null) - return; - - if (ex is not RpcException { - Status.StatusCode: StatusCode.Unavailable - }) { - receivedAllEvents.TrySetException(ex); - } - else { - var _ = ResubscribeAsync(); - } - } - - async Task ResubscribeAsync() { - try { - var sub = await _fixture.Client.SubscribeToStreamAsync( - streamName, - receivedEvents.Any() - ? FromStream.After(receivedEvents[^1].OriginalEventNumber) - : FromStream.Start, - EventAppeared, - subscriptionDropped: SubscriptionDropped - ); - - resubscribed.SetResult(sub); - } - catch (Exception ex) { - ex = ex.GetBaseException(); - - if (ex is RpcException) { - await Task.Delay(200); - var _ = ResubscribeAsync(); - } - else { - resubscribed.SetException(ex); - } - } - } - } - - public class Fixture : EventStoreClientFixture { - public Fixture() : base( - env: new() { - ["EVENTSTORE_MEM_DB"] = "false" - } - ) { - Settings.ConnectivitySettings.DiscoveryInterval = TimeSpan.FromMilliseconds(100); - Settings.ConnectivitySettings.GossipTimeout = TimeSpan.FromMilliseconds(100); - } - - protected override Task Given() => Task.CompletedTask; - - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/sending_and_receiving_large_messages.cs b/test/EventStore.Client.Streams.Tests/sending_and_receiving_large_messages.cs deleted file mode 100644 index 43e760968..000000000 --- a/test/EventStore.Client.Streams.Tests/sending_and_receiving_large_messages.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Grpc.Core; - -namespace EventStore.Client.Streams.Tests; - -public class sending_and_receiving_large_messages : IClassFixture { - readonly Fixture _fixture; - - public sending_and_receiving_large_messages(Fixture fixture, ITestOutputHelper outputHelper) { - _fixture = fixture; - _fixture.CaptureLogs(outputHelper); - } - - [Fact] - public async Task over_the_hard_limit() { - var streamName = _fixture.GetStreamName(); - var ex = await Assert.ThrowsAsync( - () => _fixture.Client.AppendToStreamAsync( - streamName, - StreamState.NoStream, - _fixture.LargeEvent - ) - ); - - Assert.Equal(StatusCode.ResourceExhausted, ex.StatusCode); - } - - public class Fixture : EventStoreClientFixture { - const int MaximumSize = 16 * 1024 * 1024 - 10000; // magic number - - public Fixture() : base( - env: new() { - ["EVENTSTORE_MAX_APPEND_SIZE"] = $"{MaximumSize}" - } - ) { } - - public IEnumerable LargeEvent => - CreateTestEvents() - .Select(e => new EventData(e.EventId, "-", new byte[MaximumSize + 1])); - - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/stream_metadata_with_timeout.cs b/test/EventStore.Client.Streams.Tests/stream_metadata_with_timeout.cs deleted file mode 100644 index e7c57df03..000000000 --- a/test/EventStore.Client.Streams.Tests/stream_metadata_with_timeout.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Grpc.Core; - -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "Network")] -public class stream_metadata_with_timeout : IClassFixture { - readonly Fixture _fixture; - - public stream_metadata_with_timeout(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task set_with_any_stream_revision_fails_when_operation_expired() { - var stream = _fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync( - () => - _fixture.Client.SetStreamMetadataAsync( - stream, - StreamState.Any, - new(), - deadline: TimeSpan.Zero - ) - ); - - Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); - } - - [Fact] - public async Task set_with_stream_revision_fails_when_operation_expired() { - var stream = _fixture.GetStreamName(); - - var rpcException = await Assert.ThrowsAsync( - () => - _fixture.Client.SetStreamMetadataAsync( - stream, - new StreamRevision(0), - new(), - deadline: TimeSpan.Zero - ) - ); - - Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); - } - - [Fact] - public async Task get_fails_when_operation_expired() { - var stream = _fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync( - () => - _fixture.Client.GetStreamMetadataAsync(stream, TimeSpan.Zero) - ); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/subscribe_resolve_link_to.cs b/test/EventStore.Client.Streams.Tests/subscribe_resolve_link_to.cs deleted file mode 100644 index 51be702a0..000000000 --- a/test/EventStore.Client.Streams.Tests/subscribe_resolve_link_to.cs +++ /dev/null @@ -1,204 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -public class subscribe_resolve_link_to : IAsyncLifetime { - readonly Fixture _fixture; - - public subscribe_resolve_link_to(ITestOutputHelper outputHelper) { - _fixture = new(); - _fixture.CaptureLogs(outputHelper); - } - - public Task InitializeAsync() => _fixture.InitializeAsync(); - public Task DisposeAsync() => _fixture.DisposeAsync(); - - [Fact] - public async Task stream_subscription() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(20).ToArray(); - - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - var beforeEvents = events.Take(10); - var afterEvents = events.Skip(10); - - using var enumerator = events.AsEnumerable().GetEnumerator(); - - enumerator.MoveNext(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, beforeEvents) - .WithTimeout(); - - using var subscription = await _fixture.Client - .SubscribeToStreamAsync( - $"$et-{EventStoreClientFixtureBase.TestEventType}", - FromStream.Start, - EventAppeared, - true, - SubscriptionDropped, - TestCredentials.Root - ) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, afterEvents) - .WithTimeout(); - - await appeared.Task.WithTimeout(); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - try { - Assert.Equal(enumerator.Current.EventId, e.Event.EventId); - if (!enumerator.MoveNext()) - appeared.TrySetResult(true); - } - catch (Exception ex) { - appeared.TrySetException(ex); - throw; - } - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task all_subscription() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(20).ToArray(); - - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - var beforeEvents = events.Take(10); - var afterEvents = events.Skip(10); - - using var enumerator = events.AsEnumerable().GetEnumerator(); - - enumerator.MoveNext(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, beforeEvents) - .WithTimeout(); - - using var subscription = await _fixture.Client.SubscribeToAllAsync( - FromAll.Start, - EventAppeared, - true, - SubscriptionDropped, - userCredentials: TestCredentials.Root - ) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, afterEvents).WithTimeout(); - - await appeared.Task.WithTimeout(); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - if (e.OriginalEvent.EventStreamId != $"$et-{EventStoreClientFixtureBase.TestEventType}") - return Task.CompletedTask; - - try { - Assert.Equal(enumerator.Current.EventId, e.Event.EventId); - if (!enumerator.MoveNext()) - appeared.TrySetResult(true); - } - catch (Exception ex) { - appeared.TrySetException(ex); - throw; - } - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task all_filtered_subscription() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(20).ToArray(); - - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - var beforeEvents = events.Take(10); - var afterEvents = events.Skip(10); - - using var enumerator = events.AsEnumerable().GetEnumerator(); - - enumerator.MoveNext(); - - var result = await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, beforeEvents) - .WithTimeout(); - - using var subscription = await _fixture.Client.SubscribeToAllAsync( - FromAll.Start, - EventAppeared, - true, - SubscriptionDropped, - new(StreamFilter.Prefix($"$et-{EventStoreClientFixtureBase.TestEventType}")), - TestCredentials.Root - ) - .WithTimeout(); - - result = await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, afterEvents) - .WithTimeout(); - - await appeared.Task.WithTimeout(); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - if (e.OriginalEvent.EventStreamId != $"$et-{EventStoreClientFixtureBase.TestEventType}") - return Task.CompletedTask; - - try { - Assert.Equal(enumerator.Current.EventId, e.Event.EventId); - if (!enumerator.MoveNext()) - appeared.TrySetResult(true); - } - catch (Exception ex) { - appeared.TrySetException(ex); - throw; - } - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - public class Fixture : EventStoreClientFixture { - public Fixture() : base( - env: new() { - ["EVENTSTORE_RUN_PROJECTIONS"] = "All", - ["EVENTSTORE_START_STANDARD_PROJECTIONS"] = "True" - } - ) { } - - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_all.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_all.cs deleted file mode 100644 index 68435b980..000000000 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_all.cs +++ /dev/null @@ -1,178 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "LongRunning")] -public class subscribe_to_all : IAsyncLifetime { - readonly Fixture _fixture; - - /// - /// This class does not implement IClassFixture because it checks $all, and we want a fresh Node for each test. - /// - public subscribe_to_all(ITestOutputHelper outputHelper) { - _fixture = new(); - _fixture.CaptureLogs(outputHelper); - } - - public Task InitializeAsync() => _fixture.InitializeAsync(); - public Task DisposeAsync() => _fixture.DisposeAsync(); - - [Fact] - public async Task calls_subscription_dropped_when_disposed() { - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var subscription = await _fixture.Client.SubscribeToAllAsync( - FromAll.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) => Task.CompletedTask; - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task calls_subscription_dropped_when_error_processing_event() { - var stream = _fixture.GetStreamName(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - var expectedException = new Exception("Error"); - - using var subscription = await _fixture.Client.SubscribeToAllAsync( - FromAll.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.SubscriberError, reason); - Assert.Same(expectedException, ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) => Task.FromException(expectedException); - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task subscribe_to_empty_database() { - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var subscription = await _fixture.Client.SubscribeToAllAsync( - FromAll.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - Assert.False(appeared.Task.IsCompleted); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - if (!SystemStreams.IsSystemStream(e.OriginalStreamId)) - appeared.TrySetResult(true); - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task reads_all_existing_events_and_keep_listening_to_new_ones() { - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - var appearedEvents = new List(); - var beforeEvents = _fixture.CreateTestEvents(10).ToArray(); - var afterEvents = _fixture.CreateTestEvents(10).ToArray(); - - foreach (var @event in beforeEvents) - await _fixture.Client.AppendToStreamAsync( - $"stream-{@event.EventId:n}", - StreamState.NoStream, - new[] { @event } - ); - - using var subscription = await _fixture.Client.SubscribeToAllAsync( - FromAll.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - foreach (var @event in afterEvents) - await _fixture.Client.AppendToStreamAsync( - $"stream-{@event.EventId:n}", - StreamState.NoStream, - new[] { @event } - ); - - await appeared.Task.WithTimeout(); - - Assert.Equal( - beforeEvents.Concat(afterEvents).Select(x => x.EventId), - appearedEvents.Select(x => x.EventId) - ); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - if (!SystemStreams.IsSystemStream(e.OriginalStreamId)) { - appearedEvents.Add(e.Event); - - if (appearedEvents.Count >= beforeEvents.Length + afterEvents.Length) - appeared.TrySetResult(true); - } - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => - Client.SetStreamMetadataAsync( - SystemStreams.AllStream, - StreamState.NoStream, - new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root - ); - - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered.cs deleted file mode 100644 index 5e33b781f..000000000 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered.cs +++ /dev/null @@ -1,196 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -public class subscribe_to_all_filtered : IAsyncLifetime { - readonly Fixture _fixture; - - public subscribe_to_all_filtered(ITestOutputHelper outputHelper) { - _fixture = new(); - _fixture.CaptureLogs(outputHelper); - } - - public Task InitializeAsync() => _fixture.InitializeAsync(); - - public Task DisposeAsync() => _fixture.DisposeAsync(); - - public static IEnumerable FilterCases() => Filters.All.Select(filter => new object[] { filter }); - - [Theory] - [MemberData(nameof(FilterCases))] - public async Task reads_all_existing_events(string filterName) { - var streamPrefix = _fixture.GetStreamName(); - var (getFilter, prepareEvent) = Filters.GetFilter(filterName); - - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - var checkpointSeen = new TaskCompletionSource(); - var filter = getFilter(streamPrefix); - var events = _fixture.CreateTestEvents(20).Select(e => prepareEvent(streamPrefix, e)) - .ToArray(); - - using var enumerator = events.OfType().GetEnumerator(); - enumerator.MoveNext(); - - await _fixture.Client.AppendToStreamAsync( - Guid.NewGuid().ToString(), - StreamState.NoStream, - _fixture.CreateTestEvents(256) - ); - - foreach (var e in events) - await _fixture.Client.AppendToStreamAsync( - $"{streamPrefix}_{Guid.NewGuid():n}", - StreamState.NoStream, - new[] { e } - ); - - using var subscription = await _fixture.Client.SubscribeToAllAsync( - FromAll.Start, - EventAppeared, - false, - SubscriptionDropped, - new(filter, 5, CheckpointReached) - ) - .WithTimeout(); - - await Task.WhenAll(appeared.Task, checkpointSeen.Task).WithTimeout(); - - Assert.False(dropped.Task.IsCompleted); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription _, ResolvedEvent e, CancellationToken ct) { - try { - Assert.Equal(enumerator.Current.EventId, e.OriginalEvent.EventId); - if (!enumerator.MoveNext()) - appeared.TrySetResult(true); - } - catch (Exception ex) { - appeared.TrySetException(ex); - throw; - } - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) { - dropped.SetResult((reason, ex)); - if (reason != SubscriptionDroppedReason.Disposed) { - appeared.TrySetException(ex!); - checkpointSeen.TrySetException(ex!); - } - } - - Task CheckpointReached(StreamSubscription _, Position position, CancellationToken ct) { - checkpointSeen.TrySetResult(true); - - return Task.CompletedTask; - } - } - - [Theory] - [MemberData(nameof(FilterCases))] - public async Task reads_all_existing_events_and_keep_listening_to_new_ones(string filterName) { - var streamPrefix = _fixture.GetStreamName(); - var (getFilter, prepareEvent) = Filters.GetFilter(filterName); - - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - var checkpointSeen = new TaskCompletionSource(); - var filter = getFilter(streamPrefix); - var events = _fixture.CreateTestEvents(20).Select(e => prepareEvent(streamPrefix, e)) - .ToArray(); - - var beforeEvents = events.Take(10); - var afterEvents = events.Skip(10); - - using var enumerator = events.OfType().GetEnumerator(); - enumerator.MoveNext(); - - await _fixture.Client.AppendToStreamAsync( - Guid.NewGuid().ToString(), - StreamState.NoStream, - _fixture.CreateTestEvents(256) - ); - - foreach (var e in beforeEvents) - await _fixture.Client.AppendToStreamAsync( - $"{streamPrefix}_{Guid.NewGuid():n}", - StreamState.NoStream, - new[] { e } - ); - - using var subscription = await _fixture.Client.SubscribeToAllAsync( - FromAll.Start, - EventAppeared, - false, - SubscriptionDropped, - new(filter, 5, CheckpointReached) - ) - .WithTimeout(); - - foreach (var e in afterEvents) - await _fixture.Client.AppendToStreamAsync( - $"{streamPrefix}_{Guid.NewGuid():n}", - StreamState.NoStream, - new[] { e } - ); - - await Task.WhenAll(appeared.Task, checkpointSeen.Task).WithTimeout(); - - Assert.False(dropped.Task.IsCompleted); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription _, ResolvedEvent e, CancellationToken ct) { - try { - Assert.Equal(enumerator.Current.EventId, e.OriginalEvent.EventId); - if (!enumerator.MoveNext()) - appeared.TrySetResult(true); - } - catch (Exception ex) { - appeared.TrySetException(ex); - throw; - } - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) { - dropped.SetResult((reason, ex)); - if (reason != SubscriptionDroppedReason.Disposed) { - appeared.TrySetException(ex!); - checkpointSeen.TrySetException(ex!); - } - } - - Task CheckpointReached(StreamSubscription _, Position position, CancellationToken ct) { - checkpointSeen.TrySetResult(true); - - return Task.CompletedTask; - } - } - - public class Fixture : EventStoreClientFixture { - public const string FilteredOutStream = nameof(FilteredOutStream); - - protected override Task Given() => - Client.SetStreamMetadataAsync( - SystemStreams.AllStream, - StreamState.Any, - new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root - ); - - protected override Task When() => Client.AppendToStreamAsync(FilteredOutStream, StreamState.NoStream, CreateTestEvents(10)); - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered_live.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered_live.cs deleted file mode 100644 index 2e20cb523..000000000 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered_live.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -public class subscribe_to_all_filtered_live : IAsyncLifetime { - readonly Fixture _fixture; - - public subscribe_to_all_filtered_live(ITestOutputHelper outputHelper) { - _fixture = new(); - _fixture.CaptureLogs(outputHelper); - } - - public Task InitializeAsync() => _fixture.InitializeAsync(); - - public Task DisposeAsync() => _fixture.DisposeAsync(); - - public static IEnumerable FilterCases() => Filters.All.Select(filter => new object[] { filter }); - - [Theory] - [MemberData(nameof(FilterCases))] - public async Task does_not_read_all_events_but_keep_listening_to_new_ones(string filterName) { - var streamPrefix = _fixture.GetStreamName(); - var (getFilter, prepareEvent) = Filters.GetFilter(filterName); - - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - var filter = getFilter(streamPrefix); - var events = _fixture.CreateTestEvents(20).Select(e => prepareEvent(streamPrefix, e)) - .ToArray(); - - var beforeEvents = events.Take(10); - var afterEvents = events.Skip(10); - - using var enumerator = afterEvents.OfType().GetEnumerator(); - enumerator.MoveNext(); - - foreach (var e in beforeEvents) - await _fixture.Client.AppendToStreamAsync( - $"{streamPrefix}_{Guid.NewGuid():n}", - StreamState.NoStream, - new[] { e } - ); - - using var subscription = await _fixture.Client.SubscribeToAllAsync( - FromAll.End, - EventAppeared, - false, - SubscriptionDropped, - new(filter) - ) - .WithTimeout(); - - foreach (var e in afterEvents) - await _fixture.Client.AppendToStreamAsync( - $"{streamPrefix}_{Guid.NewGuid():n}", - StreamState.NoStream, - new[] { e } - ); - - await appeared.Task.WithTimeout(); - - Assert.False(dropped.Task.IsCompleted); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription _, ResolvedEvent e, CancellationToken ct) { - try { - Assert.Equal(enumerator.Current.EventId, e.OriginalEvent.EventId); - if (!enumerator.MoveNext()) - appeared.TrySetResult(true); - } - catch (Exception ex) { - appeared.TrySetException(ex); - throw; - } - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - public class Fixture : EventStoreClientFixture { - public const string FilteredOutStream = nameof(FilteredOutStream); - - protected override Task Given() => - Client.SetStreamMetadataAsync( - SystemStreams.AllStream, - StreamState.Any, - new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root - ); - - protected override Task When() => Client.AppendToStreamAsync(FilteredOutStream, StreamState.NoStream, CreateTestEvents(10)); - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered_with_position.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered_with_position.cs deleted file mode 100644 index 978be0474..000000000 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered_with_position.cs +++ /dev/null @@ -1,118 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -public class subscribe_to_all_filtered_with_position : IAsyncLifetime { - readonly Fixture _fixture; - - public subscribe_to_all_filtered_with_position(ITestOutputHelper outputHelper) { - _fixture = new(); - _fixture.CaptureLogs(outputHelper); - } - - public Task InitializeAsync() => _fixture.InitializeAsync(); - - public Task DisposeAsync() => _fixture.DisposeAsync(); - - public static IEnumerable FilterCases() => Filters.All.Select(filter => new object[] { filter }); - - [Theory] - [MemberData(nameof(FilterCases))] - public async Task reads_all_existing_events_and_keep_listening_to_new_ones(string filterName) { - var streamPrefix = _fixture.GetStreamName(); - var (getFilter, prepareEvent) = Filters.GetFilter(filterName); - - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - var checkpointSeen = new TaskCompletionSource(); - var filter = getFilter(streamPrefix); - var events = _fixture.CreateTestEvents(20).Select(e => prepareEvent(streamPrefix, e)) - .ToArray(); - - var beforeEvents = events.Take(10); - var afterEvents = events.Skip(10); - - using var enumerator = afterEvents.OfType().GetEnumerator(); - enumerator.MoveNext(); - - foreach (var e in beforeEvents) - await _fixture.Client.AppendToStreamAsync( - $"{streamPrefix}_{Guid.NewGuid():n}", - StreamState.NoStream, - new[] { e } - ); - - var writeResult = await _fixture.Client.AppendToStreamAsync( - "checkpoint", - StreamState.NoStream, - _fixture.CreateTestEvents() - ); - - await _fixture.Client.AppendToStreamAsync( - Guid.NewGuid().ToString(), - StreamState.NoStream, - _fixture.CreateTestEvents(256) - ); - - using var subscription = await _fixture.Client.SubscribeToAllAsync( - FromAll.After(writeResult.LogPosition), - EventAppeared, - false, - SubscriptionDropped, - new(filter, 4, CheckpointReached) - ) - .WithTimeout(); - - foreach (var e in afterEvents) - await _fixture.Client.AppendToStreamAsync( - $"{streamPrefix}_{Guid.NewGuid():n}", - StreamState.NoStream, - new[] { e } - ); - - await Task.WhenAll(appeared.Task, checkpointSeen.Task).WithTimeout(); - - Assert.False(dropped.Task.IsCompleted); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription _, ResolvedEvent e, CancellationToken ct) { - try { - Assert.Equal(enumerator.Current.EventId, e.OriginalEvent.EventId); - if (!enumerator.MoveNext()) - appeared.TrySetResult(true); - } - catch (Exception ex) { - appeared.TrySetException(ex); - throw; - } - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - - Task CheckpointReached(StreamSubscription _, Position position, CancellationToken ct) { - checkpointSeen.TrySetResult(true); - - return Task.CompletedTask; - } - } - - public class Fixture : EventStoreClientFixture { - public const string FilteredOutStream = nameof(FilteredOutStream); - - protected override Task Given() => - Client.SetStreamMetadataAsync( - SystemStreams.AllStream, - StreamState.Any, - new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root - ); - - protected override Task When() => Client.AppendToStreamAsync(FilteredOutStream, StreamState.NoStream, CreateTestEvents(10)); - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_all_live.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_all_live.cs deleted file mode 100644 index f64cf8dd0..000000000 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_all_live.cs +++ /dev/null @@ -1,151 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "LongRunning")] -public class subscribe_to_all_live : IAsyncLifetime { - readonly Fixture _fixture; - - /// - /// This class does not implement IClassFixture because it checks $all, and we want a fresh Node for each test. - /// - public subscribe_to_all_live(ITestOutputHelper outputHelper) { - _fixture = new(); - _fixture.CaptureLogs(outputHelper); - } - - public Task InitializeAsync() => _fixture.InitializeAsync(); - public Task DisposeAsync() => _fixture.DisposeAsync(); - - [Fact] - public async Task calls_subscription_dropped_when_disposed() { - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var subscription = await _fixture.Client - .SubscribeToAllAsync(FromAll.End, EventAppeared, false, SubscriptionDropped) - .WithTimeout(); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) => Task.CompletedTask; - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task calls_subscription_dropped_when_error_processing_event() { - var stream = _fixture.GetStreamName(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - var expectedException = new Exception("Error"); - - using var subscription = await _fixture.Client - .SubscribeToAllAsync(FromAll.End, EventAppeared, false, SubscriptionDropped) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.SubscriberError, reason); - Assert.Same(expectedException, ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) => Task.FromException(expectedException); - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task subscribe_to_empty_database() { - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var subscription = await _fixture.Client - .SubscribeToAllAsync(FromAll.End, EventAppeared, false, SubscriptionDropped) - .WithTimeout(); - - Assert.False(appeared.Task.IsCompleted); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - if (!SystemStreams.IsSystemStream(e.OriginalStreamId)) - appeared.TrySetResult(true); - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task does_not_read_existing_events_but_keep_listening_to_new_ones() { - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - var appearedEvents = new List(); - var afterEvents = _fixture.CreateTestEvents(10).ToArray(); - - using var subscription = await _fixture.Client - .SubscribeToAllAsync(FromAll.End, EventAppeared, false, SubscriptionDropped) - .WithTimeout(); - - foreach (var @event in afterEvents) - await _fixture.Client.AppendToStreamAsync( - $"stream-{@event.EventId:n}", - StreamState.NoStream, - new[] { @event } - ); - - await appeared.Task.WithTimeout(); - - Assert.Equal(afterEvents.Select(x => x.EventId), appearedEvents.Select(x => x.EventId)); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - if (!SystemStreams.IsSystemStream(e.OriginalStreamId)) { - appearedEvents.Add(e.Event); - - if (appearedEvents.Count >= afterEvents.Length) - appeared.TrySetResult(true); - } - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => - Client.SetStreamMetadataAsync( - SystemStreams.AllStream, - StreamState.NoStream, - new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root - ); - - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_all_with_position.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_all_with_position.cs deleted file mode 100644 index d732e7808..000000000 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_all_with_position.cs +++ /dev/null @@ -1,206 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "LongRunning")] -public class subscribe_to_all_with_position : IAsyncLifetime { - readonly Fixture _fixture; - - /// - /// This class does not implement IClassFixture because it checks $all, and we want a fresh Node for each test. - /// - public subscribe_to_all_with_position(ITestOutputHelper outputHelper) { - _fixture = new(); - _fixture.CaptureLogs(outputHelper); - } - - public Task InitializeAsync() => _fixture.InitializeAsync(); - public Task DisposeAsync() => _fixture.DisposeAsync(); - - [Fact] - public async Task calls_subscription_dropped_when_disposed() { - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - var firstEvent = await _fixture.Client.ReadAllAsync(Direction.Forwards, Position.Start, 1) - .FirstOrDefaultAsync(); - - using var subscription = await _fixture.Client - .SubscribeToAllAsync( - FromAll.After(firstEvent.OriginalEvent.Position), - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) => Task.CompletedTask; - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task calls_subscription_dropped_when_error_processing_event() { - var stream = _fixture.GetStreamName(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - var expectedException = new Exception("Error"); - - var firstEvent = await _fixture.Client.ReadAllAsync(Direction.Forwards, Position.Start, 1) - .FirstOrDefaultAsync(); - - using var subscription = await _fixture.Client - .SubscribeToAllAsync( - FromAll.After(firstEvent.OriginalEvent.Position), - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents(2)); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.SubscriberError, reason); - Assert.Same(expectedException, ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) => Task.FromException(expectedException); - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task subscribe_to_empty_database() { - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - var firstEvent = await _fixture.Client.ReadAllAsync(Direction.Forwards, Position.Start, 1) - .FirstOrDefaultAsync(); - - using var subscription = await _fixture.Client - .SubscribeToAllAsync( - FromAll.After(firstEvent.OriginalEvent.Position), - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - Assert.False(appeared.Task.IsCompleted); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - if (e.OriginalEvent.Position == firstEvent.OriginalEvent.Position) { - appeared.TrySetException(new Exception()); - return Task.CompletedTask; - } - - if (!SystemStreams.IsSystemStream(e.OriginalStreamId)) - appeared.TrySetResult(true); - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task reads_all_existing_events_after_position_and_keep_listening_to_new_ones() { - var events = _fixture.CreateTestEvents(20).ToArray(); - - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - var beforeEvents = events.Take(10); - var afterEvents = events.Skip(10); - - using var enumerator = events.AsEnumerable().GetEnumerator(); - - enumerator.MoveNext(); - - var position = await _fixture.Client.ReadAllAsync(Direction.Forwards, Position.Start, 1) - .Select(x => x.OriginalEvent.Position) - .FirstAsync(); - - foreach (var @event in beforeEvents) - await _fixture.Client.AppendToStreamAsync( - $"stream-{@event.EventId:n}", - StreamState.NoStream, - new[] { @event } - ); - - using var subscription = await _fixture.Client.SubscribeToAllAsync( - FromAll.After(position), - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - foreach (var @event in afterEvents) - await _fixture.Client.AppendToStreamAsync( - $"stream-{@event.EventId:n}", - StreamState.NoStream, - new[] { @event } - ); - - await appeared.Task.WithTimeout(); - - Assert.False(dropped.Task.IsCompleted); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - if (position >= e.OriginalEvent.Position) - appeared.TrySetException(new Exception()); - - if (!SystemStreams.IsSystemStream(e.OriginalStreamId)) - try { - Assert.Equal(enumerator.Current.EventId, e.OriginalEvent.EventId); - if (!enumerator.MoveNext()) - appeared.TrySetResult(true); - } - catch (Exception ex) { - appeared.TrySetException(ex); - throw; - } - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => - Client.SetStreamMetadataAsync( - SystemStreams.AllStream, - StreamState.NoStream, - new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root - ); - - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_stream.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_stream.cs deleted file mode 100644 index f2a9df7a5..000000000 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_stream.cs +++ /dev/null @@ -1,265 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "LongRunning")] -public class subscribe_to_stream : IClassFixture { - readonly Fixture _fixture; - - public subscribe_to_stream(Fixture fixture, ITestOutputHelper outputHelper) { - _fixture = fixture; - _fixture.CaptureLogs(outputHelper); - } - - [Fact] - public async Task subscribe_to_non_existing_stream() { - var stream = _fixture.GetStreamName(); - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var subscription = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - Assert.False(appeared.Task.IsCompleted); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - appeared.TrySetResult(true); - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task subscribe_to_non_existing_stream_then_get_event() { - var stream = _fixture.GetStreamName(); - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var subscription = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - - Assert.True(await appeared.Task.WithTimeout()); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - appeared.TrySetResult(true); - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task allow_multiple_subscriptions_to_same_stream() { - var stream = _fixture.GetStreamName(); - - var appeared = new TaskCompletionSource(); - - var appearedCount = 0; - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - using var s1 = await _fixture.Client - .SubscribeToStreamAsync(stream, FromStream.Start, EventAppeared).WithTimeout(); - - using var s2 = await _fixture.Client - .SubscribeToStreamAsync(stream, FromStream.Start, EventAppeared).WithTimeout(); - - Assert.True(await appeared.Task.WithTimeout()); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - if (++appearedCount == 2) - appeared.TrySetResult(true); - - return Task.CompletedTask; - } - } - - [Fact] - public async Task calls_subscription_dropped_when_disposed() { - var stream = _fixture.GetStreamName(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var subscription = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) => Task.CompletedTask; - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task calls_subscription_dropped_when_error_processing_event() { - var stream = _fixture.GetStreamName(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - var expectedException = new Exception("Error"); - - using var subscription = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.SubscriberError, reason); - Assert.Same(expectedException, ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) => Task.FromException(expectedException); - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task reads_all_existing_events_and_keep_listening_to_new_ones() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(20).ToArray(); - - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - var beforeEvents = events.Take(10); - var afterEvents = events.Skip(10); - - using var enumerator = events.AsEnumerable().GetEnumerator(); - - enumerator.MoveNext(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, beforeEvents) - .WithTimeout(); - - using var subscription = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, afterEvents) - .WithTimeout(); - - await appeared.Task.WithTimeout(); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - try { - Assert.Equal(enumerator.Current.EventId, e.OriginalEvent.EventId); - if (!enumerator.MoveNext()) - appeared.TrySetResult(true); - } - catch (Exception ex) { - appeared.TrySetException(ex); - throw; - } - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task catches_deletions() { - var stream = _fixture.GetStreamName(); - - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var _ = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - await _fixture.Client.TombstoneAsync(stream, StreamState.NoStream); - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.ServerError, reason); - var sdex = Assert.IsType(ex); - Assert.Equal(stream, sdex.Stream); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) => Task.CompletedTask; - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - public class Fixture : EventStoreClientFixture { - public Fixture() => Events = CreateTestEvents(10).ToArray(); - - public EventData[] Events { get; } - - protected override Task Given() => Task.CompletedTask; - - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_stream_live.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_stream_live.cs deleted file mode 100644 index 6d7400815..000000000 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_stream_live.cs +++ /dev/null @@ -1,165 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "LongRunning")] -public class subscribe_to_stream_live : IClassFixture { - readonly Fixture _fixture; - - public subscribe_to_stream_live(Fixture fixture, ITestOutputHelper outputHelper) { - _fixture = fixture; - _fixture.CaptureLogs(outputHelper); - } - - [Fact] - public async Task does_not_read_existing_events_but_keep_listening_to_new_ones() { - var stream = _fixture.GetStreamName(); - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource(); - - await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - _fixture.CreateTestEvents() - ); - - using var _ = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.End, - (_, e, _) => { - appeared.TrySetResult(e.OriginalEventNumber); - return Task.CompletedTask; - }, - false, - (s, reason, ex) => dropped.TrySetResult(true) - ) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync( - stream, - new StreamRevision(0), - _fixture.CreateTestEvents() - ); - - Assert.Equal(new(1), await appeared.Task.WithTimeout()); - } - - [Fact] - public async Task subscribe_to_non_existing_stream_and_then_catch_new_event() { - var stream = _fixture.GetStreamName(); - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource(); - - using var _ = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.End, - (_, _, _) => { - appeared.TrySetResult(true); - return Task.CompletedTask; - }, - false, - (s, reason, ex) => dropped.TrySetResult(true) - ) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - _fixture.CreateTestEvents() - ); - - Assert.True(await appeared.Task.WithTimeout()); - } - - [Fact] - public async Task allow_multiple_subscriptions_to_same_stream() { - var stream = _fixture.GetStreamName(); - - var appeared = new TaskCompletionSource(); - - var appearedCount = 0; - - using var s1 = await _fixture.Client - .SubscribeToStreamAsync(stream, FromStream.End, EventAppeared) - .WithTimeout(); - - using var s2 = await _fixture.Client - .SubscribeToStreamAsync(stream, FromStream.End, EventAppeared) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - - Assert.True(await appeared.Task.WithTimeout()); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - if (++appearedCount == 2) - appeared.TrySetResult(true); - - return Task.CompletedTask; - } - } - - [Fact] - public async Task calls_subscription_dropped_when_disposed() { - var stream = _fixture.GetStreamName(); - - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var _ = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.End, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - s.Dispose(); - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Null(ex); - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - } - - [Fact] - public async Task catches_deletions() { - var stream = _fixture.GetStreamName(); - - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var _ = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.End, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - await _fixture.Client.TombstoneAsync(stream, StreamState.NoStream); - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.ServerError, reason); - var sdex = Assert.IsType(ex); - Assert.Equal(stream, sdex.Stream); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) => Task.CompletedTask; - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_stream_with_revision.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_stream_with_revision.cs deleted file mode 100644 index e86535cd6..000000000 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_stream_with_revision.cs +++ /dev/null @@ -1,248 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "LongRunning")] -public class subscribe_to_stream_with_revision : IAsyncLifetime { - readonly Fixture _fixture; - - public subscribe_to_stream_with_revision(ITestOutputHelper outputHelper) { - _fixture = new(); - _fixture.CaptureLogs(outputHelper); - } - - public Task InitializeAsync() => _fixture.InitializeAsync(); - - public Task DisposeAsync() => _fixture.DisposeAsync(); - - [Fact] - public async Task subscribe_to_non_existing_stream() { - var stream = _fixture.GetStreamName(); - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var subscription = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - Assert.False(appeared.Task.IsCompleted); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - appeared.TrySetResult(true); - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task subscribe_to_non_existing_stream_then_get_event() { - var stream = _fixture.GetStreamName(); - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var subscription = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.After(StreamPosition.Start), - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync( - stream, - StreamState.NoStream, - _fixture.CreateTestEvents(2) - ); - - Assert.True(await appeared.Task.WithTimeout()); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - if (e.OriginalEvent.EventNumber == StreamPosition.Start) - appeared.TrySetException(new Exception()); - else - appeared.TrySetResult(true); - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task allow_multiple_subscriptions_to_same_stream() { - var stream = _fixture.GetStreamName(); - - var appeared = new TaskCompletionSource(); - - var appearedCount = 0; - - using var s1 = await _fixture.Client - .SubscribeToStreamAsync(stream, FromStream.After(StreamPosition.Start), EventAppeared) - .WithTimeout(); - - using var s2 = await _fixture.Client - .SubscribeToStreamAsync(stream, FromStream.After(StreamPosition.Start), EventAppeared) - .WithTimeout(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents(2)); - - Assert.True(await appeared.Task.WithTimeout()); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - if (e.OriginalEvent.EventNumber == StreamPosition.Start) { - appeared.TrySetException(new Exception()); - return Task.CompletedTask; - } - - if (++appearedCount == 2) - appeared.TrySetResult(true); - - return Task.CompletedTask; - } - } - - [Fact] - public async Task calls_subscription_dropped_when_disposed() { - var stream = _fixture.GetStreamName(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - using var subscription = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - if (dropped.Task.IsCompleted) - Assert.False(dropped.Task.IsCompleted, dropped.Task.Result.ToString()); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) => Task.CompletedTask; - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task calls_subscription_dropped_when_error_processing_event() { - var stream = _fixture.GetStreamName(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - var expectedException = new Exception("Error"); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents(2)); - - using var subscription = await _fixture.Client.SubscribeToStreamAsync( - stream, - FromStream.Start, - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.SubscriberError, reason); - Assert.Same(expectedException, ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) => Task.FromException(expectedException); - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - [Fact] - public async Task reads_all_existing_events_and_keep_listening_to_new_ones() { - var stream = _fixture.GetStreamName(); - - var events = _fixture.CreateTestEvents(20).ToArray(); - - var appeared = new TaskCompletionSource(); - var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); - - var beforeEvents = events.Take(10); - var afterEvents = events.Skip(10); - - using var enumerator = events.AsEnumerable().GetEnumerator(); - - enumerator.MoveNext(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, beforeEvents); - - using var subscription = await _fixture.Client - .SubscribeToStreamAsync( - stream, - FromStream.After(StreamPosition.Start), - EventAppeared, - false, - SubscriptionDropped - ) - .WithTimeout(); - - var writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, afterEvents); - - await appeared.Task.WithTimeout(); - - subscription.Dispose(); - - var (reason, ex) = await dropped.Task.WithTimeout(); - - Assert.Equal(SubscriptionDroppedReason.Disposed, reason); - Assert.Null(ex); - - Task EventAppeared(StreamSubscription s, ResolvedEvent e, CancellationToken ct) { - try { - Assert.Equal(enumerator.Current.EventId, e.OriginalEvent.EventId); - if (!enumerator.MoveNext()) - appeared.TrySetResult(true); - } - catch (Exception ex) { - appeared.TrySetException(ex); - throw; - } - - return Task.CompletedTask; - } - - void SubscriptionDropped(StreamSubscription s, SubscriptionDroppedReason reason, Exception? ex) => dropped.SetResult((reason, ex)); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Streams.Tests/when_having_max_count_set_for_stream.cs b/test/EventStore.Client.Streams.Tests/when_having_max_count_set_for_stream.cs deleted file mode 100644 index 4a4afc827..000000000 --- a/test/EventStore.Client.Streams.Tests/when_having_max_count_set_for_stream.cs +++ /dev/null @@ -1,129 +0,0 @@ -namespace EventStore.Client.Streams.Tests; - -[Trait("Category", "LongRunning")] -public class when_having_max_count_set_for_stream : IClassFixture { - readonly Fixture _fixture; - - public when_having_max_count_set_for_stream(Fixture fixture) => _fixture = fixture; - - [Fact] - public async Task read_stream_forwards_respects_max_count() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.SetStreamMetadataAsync(stream, StreamState.NoStream, new(3)); - - var expected = _fixture.CreateTestEvents(5).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, expected); - - var actual = await _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 100) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.Equal(3, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(2).ToArray(), actual)); - } - - [Fact] - public async Task read_stream_backwards_respects_max_count() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.SetStreamMetadataAsync(stream, StreamState.NoStream, new(3)); - - var expected = _fixture.CreateTestEvents(5).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, expected); - - var actual = await _fixture.Client.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 100) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.Equal(3, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(2).Reverse().ToArray(), actual)); - } - - [Fact] - public async Task after_setting_less_strict_max_count_read_stream_forward_reads_more_events() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.SetStreamMetadataAsync(stream, StreamState.NoStream, new(3)); - - var expected = _fixture.CreateTestEvents(5).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, expected); - - await _fixture.Client.SetStreamMetadataAsync(stream, new StreamRevision(0), new(4)); - - var actual = await _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 100) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.Equal(4, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(1).ToArray(), actual)); - } - - [Fact] - public async Task after_setting_more_strict_max_count_read_stream_forward_reads_less_events() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.SetStreamMetadataAsync(stream, StreamState.NoStream, new(3)); - - var expected = _fixture.CreateTestEvents(5).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, expected); - - await _fixture.Client.SetStreamMetadataAsync(stream, new StreamRevision(0), new(2)); - - var actual = await _fixture.Client.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 100) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.Equal(2, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(3).ToArray(), actual)); - } - - [Fact] - public async Task after_setting_less_strict_max_count_read_stream_backwards_reads_more_events() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.SetStreamMetadataAsync(stream, StreamState.NoStream, new(3)); - - var expected = _fixture.CreateTestEvents(5).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, expected); - - await _fixture.Client.SetStreamMetadataAsync(stream, new StreamRevision(0), new(4)); - - var actual = await _fixture.Client.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 100) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.Equal(4, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(1).Reverse().ToArray(), actual)); - } - - [Fact] - public async Task after_setting_more_strict_max_count_read_stream_backwards_reads_less_events() { - var stream = _fixture.GetStreamName(); - - await _fixture.Client.SetStreamMetadataAsync(stream, StreamState.NoStream, new(3)); - - var expected = _fixture.CreateTestEvents(5).ToArray(); - - await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, expected); - - await _fixture.Client.SetStreamMetadataAsync(stream, new StreamRevision(0), new(2)); - - var actual = await _fixture.Client.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 100) - .Select(x => x.Event) - .ToArrayAsync(); - - Assert.Equal(2, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(3).Reverse().ToArray(), actual)); - } - - public class Fixture : EventStoreClientFixture { - protected override Task Given() => Task.CompletedTask; - protected override Task When() => Task.CompletedTask; - } -} \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/.env b/test/EventStore.Client.Tests.Common/.env index da2b09379..81d6ca0c4 100644 --- a/test/EventStore.Client.Tests.Common/.env +++ b/test/EventStore.Client.Tests.Common/.env @@ -1,19 +1,11 @@ ES_CERTS_CLUSTER=./certs-cluster ES_DOCKER_TAG=ci -#EVENTSTORE_CLUSTER_SIZE=3 -#EVENTSTORE_INT_TCP_PORT=1112 +#EVENTSTORE_MEM_DB=true #EVENTSTORE_HTTP_PORT=2113 +#EVENTSTORE_LOG_LEVEL=Information +#EVENTSTORE_DISABLE_LOG_FILE=true +#EVENTSTORE_RUN_PROJECTIONS=None +#EVENTSTORE_START_STANDARD_PROJECTIONS=true #EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/etc/eventstore/certs/ca -#EVENTSTORE_DISCOVER_VIA_DNS=false -#EVENTSTORE_ENABLE_EXTERNAL_TCP=false -#EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true -#EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE=10000 -# -## pass through from environment -#EVENTSTORE_DB_LOG_FORMAT=V2 -#EVENTSTORE_LOG_LEVEL=Verbose -#EVENTSTORE_MAX_APPEND_SIZE -#EVENTSTORE_MEM_DB=false -#EVENTSTORE_RUN_PROJECTIONS=false -#EVENTSTORE_START_STANDARD_PROJECTIONS=false +#EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=false diff --git a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj index 7df9db71a..ba1a806f8 100644 --- a/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj +++ b/test/EventStore.Client.Tests.Common/EventStore.Client.Tests.Common.csproj @@ -11,30 +11,31 @@ - - - - - - - + - + - - + + - - + + - - + - + + + + + + + + + certs\%(RecursiveDir)/%(FileName)%(Extension) diff --git a/test/EventStore.Client.Tests.Common/Extensions/TaskExtensions.cs b/test/EventStore.Client.Tests.Common/Extensions/TaskExtensions.cs index 7558c3547..b02d5ef2a 100644 --- a/test/EventStore.Client.Tests.Common/Extensions/TaskExtensions.cs +++ b/test/EventStore.Client.Tests.Common/Extensions/TaskExtensions.cs @@ -6,13 +6,11 @@ public static class TaskExtensions { public static Task WithTimeout(this Task task, TimeSpan timeout) => task.WithTimeout(Convert.ToInt32(timeout.TotalMilliseconds)); - public static async Task WithTimeout(this Task task, int timeoutMs = 10000) { - if (Debugger.IsAttached) { - timeoutMs = -1; - } + public static async Task WithTimeout(this Task task, int timeoutMs = 15000, string? message = null) { + if (Debugger.IsAttached) timeoutMs = -1; if (await Task.WhenAny(task, Task.Delay(timeoutMs)) != task) - throw new TimeoutException("Timed out waiting for task"); + throw new TimeoutException(message ?? "Timed out waiting for task"); await task; } @@ -20,14 +18,12 @@ public static async Task WithTimeout(this Task task, int timeoutMs = 10000) { public static Task WithTimeout(this Task task, TimeSpan timeout) => task.WithTimeout(Convert.ToInt32(timeout.TotalMilliseconds)); - public static async Task WithTimeout(this Task task, int timeoutMs = 10000) { - if (Debugger.IsAttached) { - timeoutMs = -1; - } + public static async Task WithTimeout(this Task task, int timeoutMs = 15000, string? message = null) { + if (Debugger.IsAttached) timeoutMs = -1; if (await Task.WhenAny(task, Task.Delay(timeoutMs)) == task) return await task; - throw new TimeoutException("Timed out waiting for task"); + throw new TimeoutException(message ?? "Timed out waiting for task"); } } \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/Facts/AnonymousAccess.cs b/test/EventStore.Client.Tests.Common/Facts/AnonymousAccess.cs index 3fd901892..575093085 100644 --- a/test/EventStore.Client.Tests.Common/Facts/AnonymousAccess.cs +++ b/test/EventStore.Client.Tests.Common/Facts/AnonymousAccess.cs @@ -1,14 +1,11 @@ namespace EventStore.Client.Tests; +[PublicAPI] public class AnonymousAccess { static readonly Version LegacySince = new(23, 6); static readonly string SkipMessage = "Anonymous access is turned off since v23.6.0!"; - public class FactAttribute : Deprecation.FactAttribute { - public FactAttribute() : base(LegacySince, SkipMessage) { } - } + public class FactAttribute() : Deprecation.FactAttribute(LegacySince, SkipMessage); - public class TheoryAttribute : Deprecation.TheoryAttribute { - public TheoryAttribute() : base(LegacySince, SkipMessage) { } - } + public class TheoryAttribute() : Deprecation.TheoryAttribute(LegacySince, SkipMessage); } \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/Facts/Deprecation.cs b/test/EventStore.Client.Tests.Common/Facts/Deprecation.cs index 111b3b016..01adee242 100644 --- a/test/EventStore.Client.Tests.Common/Facts/Deprecation.cs +++ b/test/EventStore.Client.Tests.Common/Facts/Deprecation.cs @@ -1,17 +1,10 @@ namespace EventStore.Client.Tests; +[PublicAPI] public class Deprecation { - public class FactAttribute : Xunit.FactAttribute { - readonly Version _legacySince; - readonly string _skipMessage; - - public FactAttribute(Version since, string skipMessage) { - _legacySince = since; - _skipMessage = skipMessage; - } - + public class FactAttribute(Version since, string skipMessage) : Xunit.FactAttribute { public override string? Skip { - get => EventStoreTestServer.Version >= _legacySince ? _skipMessage : null; + get => EventStoreTestServer.Version >= since ? skipMessage : null; set => throw new NotSupportedException(); } } diff --git a/test/EventStore.Client.Tests.Common/Facts/Regression.cs b/test/EventStore.Client.Tests.Common/Facts/Regression.cs index 371c00547..3c6a92f0c 100644 --- a/test/EventStore.Client.Tests.Common/Facts/Regression.cs +++ b/test/EventStore.Client.Tests.Common/Facts/Regression.cs @@ -1,32 +1,17 @@ namespace EventStore.Client.Tests; +[PublicAPI] public class Regression { - public class FactAttribute : Xunit.FactAttribute { - readonly int _major; - readonly string _skipMessage; - - public FactAttribute(int major, string skipMessage) { - _major = major; - _skipMessage = skipMessage; - } - + public class FactAttribute(int major, string skipMessage) : Xunit.FactAttribute { public override string? Skip { - get => (EventStoreTestServer.Version?.Major ?? int.MaxValue) < _major ? _skipMessage : null; + get => (EventStoreTestServer.Version?.Major ?? int.MaxValue) < major ? skipMessage : null; set => throw new NotSupportedException(); } } - public class TheoryAttribute : Xunit.TheoryAttribute { - readonly int _major; - readonly string _skipMessage; - - public TheoryAttribute(int major, string skipMessage) { - _major = major; - _skipMessage = skipMessage; - } - + public class TheoryAttribute(int major, string skipMessage) : Xunit.TheoryAttribute { public override string? Skip { - get => (EventStoreTestServer.Version?.Major ?? int.MaxValue) < _major ? _skipMessage : null; + get => (EventStoreTestServer.Version?.Major ?? int.MaxValue) < major ? skipMessage : null; set => throw new NotSupportedException(); } } diff --git a/test/EventStore.Client.Tests.Common/Facts/SupportsPSToAllFact.cs b/test/EventStore.Client.Tests.Common/Facts/SupportsPSToAllFact.cs index 9200aaf46..3f5d993fe 100644 --- a/test/EventStore.Client.Tests.Common/Facts/SupportsPSToAllFact.cs +++ b/test/EventStore.Client.Tests.Common/Facts/SupportsPSToAllFact.cs @@ -1,5 +1,8 @@ +// ReSharper disable InconsistentNaming + namespace EventStore.Client.Tests; +[PublicAPI] public class SupportsPSToAll { const int SupportedFromMajorVersion = 21; @@ -9,11 +12,7 @@ public class SupportsPSToAll { public static bool No => !Yes; public static bool Yes => (EventStoreTestServer.Version?.Major ?? int.MaxValue) >= SupportedFromMajorVersion; - public class FactAttribute : Regression.FactAttribute { - public FactAttribute() : base(SupportedFromMajorVersion, SkipMessage) { } - } + public class FactAttribute() : Regression.FactAttribute(SupportedFromMajorVersion, SkipMessage); - public class TheoryAttribute : Regression.TheoryAttribute { - public TheoryAttribute() : base(SupportedFromMajorVersion, SkipMessage) { } - } + public class TheoryAttribute() : Regression.TheoryAttribute(SupportedFromMajorVersion, SkipMessage); } \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServer.cs b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServer.cs index 98ec6ebb2..772946347 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServer.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/Base/EventStoreTestServer.cs @@ -33,7 +33,7 @@ public EventStoreTestServer( }; var env = new Dictionary { - ["EVENTSTORE_DB_LOG_FORMAT"] = GlobalEnvironment.DbLogFormat, + ["EVENTSTORE_DB_LOG_FORMAT"] = "V2", ["EVENTSTORE_MEM_DB"] = "true", ["EVENTSTORE_CHUNK_SIZE"] = (1024 * 1024).ToString(), ["EVENTSTORE_CERTIFICATE_FILE"] = "/etc/eventstore/certs/node/node.crt", diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreFixture.Helpers.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreFixture.Helpers.cs index 960434c33..e9f06b512 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreFixture.Helpers.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreFixture.Helpers.cs @@ -4,13 +4,13 @@ namespace EventStore.Client.Tests; public partial class EventStoreFixture { - const string TestEventType = "-"; + public const string TestEventType = "tst"; public T NewClient(Action configure) where T : EventStoreClientBase, new() => (T)Activator.CreateInstance(typeof(T), new object?[] { ClientSettings.With(configure) })!; - + public string GetStreamName([CallerMemberName] string? testMethod = null) => - $"{GetType().DeclaringType?.Name}.{testMethod ?? "unknown"}"; + $"{testMethod}-{Guid.NewGuid():N}"; public IEnumerable CreateTestEvents(int count = 1, string? type = null, int metadataSize = 1) => Enumerable.Range(0, count).Select(index => CreateTestEvent(index, type ?? TestEventType, metadataSize)); @@ -37,14 +37,17 @@ public Task CreateTestUsers(int count = 3, bool withoutGroups = true .Select( async user => { await Users.CreateUserAsync( - user.LoginName, - user.FullName, - user.Groups, - user.Password, + user.LoginName, user.FullName, user.Groups, user.Password, userCredentials: useUserCredentials ? user.Credentials : TestCredentials.Root ); return user; } ).WhenAll(); + + public async Task RestartService(TimeSpan delay) { + await Service.Restart(delay); + await Streams.WarmUp(); + Log.Information("Service restarted."); + } } \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreFixture.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreFixture.cs index 510293119..e59e7149d 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreFixture.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreFixture.cs @@ -1,4 +1,7 @@ using System.Net; +using Ductus.FluentDocker.Builders; +using Ductus.FluentDocker.Extensions; +using Ductus.FluentDocker.Services.Extensions; using EventStore.Client.Tests.FluentDocker; using Serilog; using static System.TimeSpan; @@ -21,6 +24,9 @@ this with { public EventStoreFixtureOptions WithoutDefaultCredentials() => this with { ClientSettings = ClientSettings.With(x => x.DefaultCredentials = null) }; + + public EventStoreFixtureOptions WithMaxAppendSize(uint maxAppendSize) => + this with { Environment = Environment.With(x => x["EVENTSTORE_MAX_APPEND_SIZE"] = $"{maxAppendSize}") }; } public delegate EventStoreFixtureOptions ConfigureFixture(EventStoreFixtureOptions options); @@ -31,7 +37,7 @@ public partial class EventStoreFixture : IAsyncLifetime, IAsyncDisposable { static EventStoreFixture() { Logging.Initialize(); - Logger = Log.ForContext(); + Logger = Serilog.Log.ForContext(); ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; } @@ -57,8 +63,14 @@ protected EventStoreFixture(ConfigureFixture configure) { List TestRuns { get; } = new(); + public ILogger Log => Logger; + public ITestService Service { get; } public EventStoreFixtureOptions Options { get; } + public Faker Faker { get; } = new Faker(); + + public Version EventStoreVersion { get; private set; } = null!; + public bool EventStoreHasLastStreamPosition { get; private set; } public EventStoreClient Streams { get; private set; } = null!; public EventStoreUserManagementClient Users { get; private set; } = null!; @@ -68,7 +80,7 @@ protected EventStoreFixture(ConfigureFixture configure) { public Func OnSetup { get; init; } = () => Task.CompletedTask; public Func OnTearDown { get; init; } = () => Task.CompletedTask; - + /// /// must test this /// @@ -87,7 +99,8 @@ protected EventStoreFixture(ConfigureFixture configure) { InterlockedBoolean WarmUpCompleted { get; } = new InterlockedBoolean(); SemaphoreSlim WarmUpGatekeeper { get; } = new(1, 1); - + + public void CaptureTestRun(ITestOutputHelper outputHelper) { var testRunId = Logging.CaptureLogs(outputHelper); TestRuns.Add(testRunId); @@ -98,6 +111,9 @@ public void CaptureTestRun(ITestOutputHelper outputHelper) { public async Task InitializeAsync() { await Service.Start(); + EventStoreVersion = GetEventStoreVersion(); + EventStoreHasLastStreamPosition = (EventStoreVersion?.Major ?? int.MaxValue) >= 21; + await WarmUpGatekeeper.WaitAsync(); try { @@ -134,6 +150,27 @@ async Task InitClient(Func action, bool execute = true) where T : await action(client); return client; } + + + static Version GetEventStoreVersion() { + const string versionPrefix = "EventStoreDB version"; + + using var cancellator = new CancellationTokenSource(FromSeconds(30)); + using var eventstore = new Builder() + .UseContainer() + .UseImage(GlobalEnvironment.DockerImage) + .Command("--version") + .Build() + .Start(); + + using var log = eventstore.Logs(true, cancellator.Token); + foreach (var line in log.ReadToEnd()) + if (line.StartsWith(versionPrefix) && + Version.TryParse(line[(versionPrefix.Length + 1)..].Split(' ')[0], out var version)) + return version; + + throw new InvalidOperationException("Could not determine server version."); + } } public async Task DisposeAsync() { @@ -151,4 +188,21 @@ public async Task DisposeAsync() { } async ValueTask IAsyncDisposable.DisposeAsync() => await DisposeAsync(); -} \ No newline at end of file +} + +[CollectionDefinition(nameof(EventStoreSharedDatabaseFixture))] +public class EventStoreSharedDatabaseFixture : ICollectionFixture { + // This class has no code, and is never created. Its purpose is simply + // to be the place to apply [CollectionDefinition] and all the + // ICollectionFixture<> interfaces. +} + +public abstract class EventStoreTests : IClassFixture where TFixture : EventStoreFixture { + protected EventStoreTests(ITestOutputHelper output, TFixture fixture) => Fixture = fixture.With(x => x.CaptureTestRun(output)); + + protected TFixture Fixture { get; } +} + +[Collection(nameof(EventStoreSharedDatabaseFixture))] +public abstract class EventStoreSharedDatabaseTests(ITestOutputHelper output, TFixture fixture) : EventStoreTests(output, fixture) + where TFixture : EventStoreFixture; \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs index d0f0bbe88..f97b852e2 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestCluster.cs @@ -26,8 +26,7 @@ public static EventStoreFixtureOptions DefaultOptions() { ["EVENTSTORE_DISCOVER_VIA_DNS"] = "false", ["EVENTSTORE_ENABLE_EXTERNAL_TCP"] = "false", ["EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE"] = "10000", - ["EVENTSTORE_STREAM_INFO_CACHE_CAPACITY"] = "10000", - ["EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP"] = "true" // why true? + ["EVENTSTORE_STREAM_INFO_CACHE_CAPACITY"] = "10000" }; return new(defaultSettings, defaultEnvironment); diff --git a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs index eae421f55..46ae316da 100644 --- a/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs +++ b/test/EventStore.Client.Tests.Common/Fixtures/EventStoreTestNode.cs @@ -31,15 +31,16 @@ public static EventStoreFixtureOptions DefaultOptions() { .With(x => x.ConnectivitySettings.DiscoveryInterval = FromSeconds(1)); var defaultEnvironment = new Dictionary(GlobalEnvironment.Variables) { - ["EVENTSTORE_ENABLE_EXTERNAL_TCP"] = "false", - ["EVENTSTORE_MEM_DB"] = "true", - ["EVENTSTORE_CHUNK_SIZE"] = (1024 * 1024).ToString(), - ["EVENTSTORE_CERTIFICATE_FILE"] = "/etc/eventstore/certs/node/node.crt", - ["EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE"] = "/etc/eventstore/certs/node/node.key", - ["EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE"] = "10000", - ["EVENTSTORE_STREAM_INFO_CACHE_CAPACITY"] = "10000", - ["EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP"] = "true", - ["EVENTSTORE_DISABLE_LOG_FILE"] = "true" + ["EVENTSTORE_ENABLE_EXTERNAL_TCP"] = "false", + ["EVENTSTORE_MEM_DB"] = "true", + ["EVENTSTORE_CHUNK_SIZE"] = (1024 * 1024).ToString(), + ["EVENTSTORE_CERTIFICATE_FILE"] = "/etc/eventstore/certs/node/node.crt", + ["EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE"] = "/etc/eventstore/certs/node/node.key", + ["EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE"] = "10000", + ["EVENTSTORE_STREAM_INFO_CACHE_CAPACITY"] = "10000", + ["EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP"] = "true", + ["EVENTSTORE_DISABLE_LOG_FILE"] = "true", + ["EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS"] = $"{NetworkPortProvider.DefaultEsdbPort}" }; // TODO SS: must find a way to enable parallel tests on CI. It works locally. @@ -72,7 +73,6 @@ protected override ContainerBuilder Configure() { .WithEnvironment(env) .MountVolume(certsPath, "/etc/eventstore/certs", MountType.ReadOnly) .ExposePort(port, 2113); - //.WaitForMessageInLog("'admin' user added to $users.", FromSeconds(60)); } /// @@ -132,13 +132,7 @@ public async Task GetNextAvailablePort(TimeSpan delay = default) { await Task.Delay(delay); } finally { - if (socket.Connected) { -#if NET5_0 - socket.Disconnect(true); -#else - await socket.DisconnectAsync(true); -#endif - } + if (socket.Connected) await socket.DisconnectAsync(true); } } } diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs b/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs index d5f62059d..e763edd35 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/FluentDockerServiceExtensions.cs @@ -30,21 +30,12 @@ public static async ValueTask WaitUntilNodesAreHealthy(this IContainerService se public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, IEnumerable services, CancellationToken cancellationToken) { var nodes = service.Containers.Where(x => services.Contains(x.Name)); -#if NET5_0 - foreach (var node in nodes) await node.WaitUntilNodesAreHealthy(cancellationToken); -#else await Parallel.ForEachAsync(nodes, cancellationToken, async (node, ct) => await node.WaitUntilNodesAreHealthy(ct)); -#endif } public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, CancellationToken cancellationToken) { var nodes = service.Containers.Where(x => x.Name.StartsWith(serviceNamePrefix)); - -#if NET5_0 - foreach (var node in nodes) await node.WaitUntilNodesAreHealthy(cancellationToken); -#else await Parallel.ForEachAsync(nodes, cancellationToken, async (node, ct) => await node.WaitUntilNodesAreHealthy(ct)); -#endif } public static async Task WaitUntilNodesAreHealthy(this ICompositeService service, string serviceNamePrefix, TimeSpan timeout) { diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/TestCompositeService.cs b/test/EventStore.Client.Tests.Common/FluentDocker/TestCompositeService.cs index 274791e0a..104827f30 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/TestCompositeService.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/TestCompositeService.cs @@ -3,4 +3,4 @@ namespace EventStore.Client.Tests.FluentDocker; -public abstract class TestCompositeService : TestService { } \ No newline at end of file +public abstract class TestCompositeService : TestService; \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/TestContainerService.cs b/test/EventStore.Client.Tests.Common/FluentDocker/TestContainerService.cs index 01ab098af..40ed937fc 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/TestContainerService.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/TestContainerService.cs @@ -3,4 +3,4 @@ namespace EventStore.Client.Tests.FluentDocker; -public abstract class TestContainerService : TestService { } \ No newline at end of file +public abstract class TestContainerService : TestService; \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs b/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs index 2f99c99fd..026e0a12c 100644 --- a/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs +++ b/test/EventStore.Client.Tests.Common/FluentDocker/TestService.cs @@ -1,4 +1,3 @@ -using Ductus.FluentDocker; using Ductus.FluentDocker.Builders; using Ductus.FluentDocker.Common; using Ductus.FluentDocker.Services; @@ -10,21 +9,13 @@ namespace EventStore.Client.Tests.FluentDocker; public interface ITestService : IAsyncDisposable { Task Start(); Task Stop(); - + Task Restart(TimeSpan delay); + + Task Restart() => Restart(TimeSpan.Zero); + void ReportStatus(); } -/// -/// This prevents multiple services from starting at the same time. -/// Required to avoid failures on creating the networks the containers are attached to. -/// -sealed class TestServiceGatekeeper { - static readonly SemaphoreSlim Semaphore = new(1, 1); - - public static Task Wait() => Semaphore.WaitAsync(); - public static void Next() => Semaphore.Release(); -} - public abstract class TestService : ITestService where TService : IService where TBuilder : BaseBuilder { ILogger Logger { get; } @@ -37,44 +28,23 @@ public abstract class TestService : ITestService where TServ public virtual async Task Start() { Logger.Information("Container service starting"); - //await TestServiceGatekeeper.Wait(); + var builder = Configure(); - try { - var builder = Configure(); - - Service = builder.Build(); - - // // for some reason fluent docker does not always create the network - // // before the service is started, so we do it manually here - // if (Service is IContainerService service) { - // var cfg = service.GetConfiguration(true); - // - // Network = Fd - // .UseNetwork(cfg.Name) - // .IsInternal() - // .Build() - // .Attach(service, true); - // - // Logger.Information("Created network {Network}", Network.Name); - // } + Service = builder.Build(); - try { - Service.Start(); - Logger.Information("Container service started"); - } - catch (Exception ex) { - throw new FluentDockerException("Failed to start container service", ex); - } + try { + Service.Start(); + Logger.Information("Container service started"); + } + catch (Exception ex) { + throw new FluentDockerException("Failed to start container service", ex); + } - try { - await OnServiceStarted(); - } - catch (Exception ex) { - throw new FluentDockerException($"{nameof(OnServiceStarted)} execution error", ex); - } + try { + await OnServiceStarted(); } - finally { - //TestServiceGatekeeper.Next(); + catch (Exception ex) { + throw new FluentDockerException($"{nameof(OnServiceStarted)} execution error", ex); } } @@ -93,6 +63,40 @@ public virtual async Task Stop() { throw new FluentDockerException("Failed to stop container service", ex); } } + + public virtual async Task Restart(TimeSpan delay) { + try { + try { + Service.Stop(); + Logger.Information("Container service stopped"); + } + catch (Exception ex) { + throw new FluentDockerException("Failed to stop container service", ex); + } + + await Task.Delay(delay); + + Logger.Information("Container service starting..."); + + try { + Service.Start(); + } + catch (Exception ex) { + throw new FluentDockerException("Failed to start container service", ex); + } + + try { + await OnServiceStarted(); + Logger.Information("Container service started"); + } + catch (Exception ex) { + throw new FluentDockerException($"{nameof(OnServiceStarted)} execution error", ex); + } + } + catch (Exception ex) { + throw new FluentDockerException("Failed to restart container service", ex); + } + } public void ReportStatus() { if (Service is IContainerService containerService) { @@ -111,8 +115,6 @@ void ReportContainerStatus(IContainerService service) { var cfg = service.GetConfiguration(true); Logger.Information("Container {Name} {State} Ports: {Ports}", service.Name, service.State, cfg.Config.ExposedPorts.Keys); } - - // var docker = Fd.Hosts().Discover().FirstOrDefault(x => x.IsNative || x.Name == "default")!; } public virtual ValueTask DisposeAsync() { @@ -125,10 +127,6 @@ public virtual ValueTask DisposeAsync() { catch { // ignored } - - /*if (Service.State != ServiceRunningState.Unknown) { - Service.Dispose(); - }*/ } catch (Exception ex) { throw new FluentDockerException("Failed to dispose of container service", ex); diff --git a/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs b/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs index 563ff4cb6..f2e1e69eb 100644 --- a/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs +++ b/test/EventStore.Client.Tests.Common/GlobalEnvironment.cs @@ -10,7 +10,6 @@ static GlobalEnvironment() { UseCluster = Application.Configuration.GetValue("ES_USE_CLUSTER"); UseExternalServer = Application.Configuration.GetValue("ES_USE_EXTERNAL_SERVER"); DockerImage = Application.Configuration.GetValue("ES_DOCKER_IMAGE")!; - DbLogFormat = Application.Configuration.GetValue("EVENTSTORE_DB_LOG_FORMAT")!; Variables = Application.Configuration.AsEnumerable() .Where(x => x.Key.StartsWith("ES_") || x.Key.StartsWith("EVENTSTORE_")) @@ -24,17 +23,17 @@ static void EnsureDefaults(IConfiguration configuration) { configuration.EnsureValue("ES_USE_EXTERNAL_SERVER", "false"); configuration.EnsureValue("ES_DOCKER_REGISTRY", "ghcr.io/eventstore/eventstore"); - configuration.EnsureValue("ES_DOCKER_TAG", "previous-lts"); + configuration.EnsureValue("ES_DOCKER_TAG", "latest"); configuration.EnsureValue("ES_DOCKER_IMAGE", $"{configuration["ES_DOCKER_REGISTRY"]}:{configuration["ES_DOCKER_TAG"]}"); configuration.EnsureValue("EVENTSTORE_MEM_DB", "false"); configuration.EnsureValue("EVENTSTORE_RUN_PROJECTIONS", "None"); configuration.EnsureValue("EVENTSTORE_START_STANDARD_PROJECTIONS", "false"); - configuration.EnsureValue("EVENTSTORE_DB_LOG_FORMAT", "V2"); - configuration.EnsureValue("EVENTSTORE_LOG_LEVEL", "Verbose"); - configuration.EnsureValue("EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH", "/etc/eventstore/certs/ca"); - + configuration.EnsureValue("EVENTSTORE_LOG_LEVEL", "Information"); configuration.EnsureValue("EVENTSTORE_DISABLE_LOG_FILE", "true"); + configuration.EnsureValue("EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH", "/etc/eventstore/certs/ca"); + configuration.EnsureValue("EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "true"); + } } @@ -43,7 +42,6 @@ static void EnsureDefaults(IConfiguration configuration) { public static bool UseCluster { get; } public static bool UseExternalServer { get; } public static string DockerImage { get; } - public static string DbLogFormat { get; } #region . Obsolete . diff --git a/test/EventStore.Client.Tests.Common/Logging.cs b/test/EventStore.Client.Tests.Common/Logging.cs index 44b2ba83b..a9df9709a 100644 --- a/test/EventStore.Client.Tests.Common/Logging.cs +++ b/test/EventStore.Client.Tests.Common/Logging.cs @@ -22,10 +22,6 @@ static Logging() { .WriteTo.Observers(x => x.Subscribe(LogEventSubject.OnNext)) .CreateLogger(); -#if GRPC_CORE - GrpcEnvironment.SetLogger(new GrpcCoreSerilogLogger(Log.Logger.ForContext())); -#endif - Ductus.FluentDocker.Services.Logging.Enabled(); AppDomain.CurrentDomain.DomainUnload += (_, _) => Log.CloseAndFlush(); @@ -56,7 +52,12 @@ Action WriteLogEvent() => logEvent.AddPropertyIfAbsent(testRunIdProperty); using var writer = new StringWriter(); DefaultFormatter.Format(logEvent, writer); - write(writer.ToString().Trim()); + try { + write(writer.ToString().Trim()); + } + catch (Exception) { + // ignored + } }; } diff --git a/test/EventStore.Client.Tests.Common/Shouldly/ShouldThrowAsyncExtensions.cs b/test/EventStore.Client.Tests.Common/Shouldly/ShouldThrowAsyncExtensions.cs new file mode 100644 index 000000000..b75778b47 --- /dev/null +++ b/test/EventStore.Client.Tests.Common/Shouldly/ShouldThrowAsyncExtensions.cs @@ -0,0 +1,12 @@ +// ReSharper disable CheckNamespace + +using System.Diagnostics; +using EventStore.Client; + +namespace Shouldly; + +[DebuggerStepThrough] +public static class ShouldThrowAsyncExtensions { + public static Task ShouldThrowAsync(this EventStoreClient.ReadStreamResult source) where TException : Exception => + source.ToArrayAsync().AsTask().ShouldThrowAsync(); +} \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/appsettings.Development.json b/test/EventStore.Client.Tests.Common/appsettings.Development.json index e459e6332..d2f3f54f5 100644 --- a/test/EventStore.Client.Tests.Common/appsettings.Development.json +++ b/test/EventStore.Client.Tests.Common/appsettings.Development.json @@ -1,9 +1,15 @@ { "Serilog": { - "MinimumLevel": { "Default": "Debug" }, - "Override": { - "Microsoft": "Warning", - "Grpc": "Verbose" + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Warning", + "Grpc": "Information", + "Grpc.Net.Client.Internal.GrpcCall": "Fatal", + "EventStore.Client.SharingProvider": "Information", + "EventStore.Client.EventStoreClient": "Information", + "EventStore.Client.SingleNodeChannelSelector": "Warning" + } }, "Enrich": ["FromLogContext", "WithThreadId"], "WriteTo": [ diff --git a/test/EventStore.Client.Tests.Common/appsettings.json b/test/EventStore.Client.Tests.Common/appsettings.json index b8155a789..7de897122 100644 --- a/test/EventStore.Client.Tests.Common/appsettings.json +++ b/test/EventStore.Client.Tests.Common/appsettings.json @@ -1,9 +1,15 @@ { "Serilog": { - "MinimumLevel": { "Default": "Debug" }, - "Override": { - "Microsoft": "Warning", - "Grpc": "Verbose" + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Warning", + "Grpc": "Information", + "Grpc.Net.Client.Internal.GrpcCall": "Fatal", + "EventStore.Client.SharingProvider": "Information", + "EventStore.Client.EventStoreClient": "Information", + "EventStore.Client.SingleNodeChannelSelector": "Warning" + } }, "Enrich": ["FromLogContext", "WithThreadId"], "WriteTo": [ diff --git a/test/EventStore.Client.Tests.Common/docker-compose.certs.yml b/test/EventStore.Client.Tests.Common/docker-compose.certs.yml new file mode 100644 index 000000000..4ff518f6a --- /dev/null +++ b/test/EventStore.Client.Tests.Common/docker-compose.certs.yml @@ -0,0 +1,35 @@ +version: "3.5" + +networks: + default: + name: eventstore-network + +services: + + volumes-provisioner: + image: hasnat/volumes-provisioner + container_name: volumes-provisioner + environment: + PROVISION_DIRECTORIES: "1000:1000:0755:/tmp/certs" + volumes: + - "${ES_CERTS_CLUSTER}:/tmp/certs" + network_mode: none + + cert-gen: + image: eventstore/es-gencert-cli:1.0.2 + container_name: cert-gen + user: "1000:1000" + entrypoint: [ "/bin/sh","-c" ] + command: + - | + es-gencert-cli create-ca -out /tmp/certs/ca + es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out /tmp/certs/node -ip-addresses 127.0.0.1 -dns-names localhost,eventstore + + es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out /tmp/certs/node1 -ip-addresses 127.0.0.1,172.30.240.11 -dns-names localhost,esdb-node1 + es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out /tmp/certs/node2 -ip-addresses 127.0.0.1,172.30.240.12 -dns-names localhost,esdb-node2 + es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out /tmp/certs/node3 -ip-addresses 127.0.0.1,172.30.240.13 -dns-names localhost,esdb-node3 + es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out /tmp/certs/node4 -ip-addresses 127.0.0.1,172.30.240.14 -dns-names localhost,esdb-node4 + volumes: + - "${ES_CERTS_CLUSTER}:/tmp/certs" + depends_on: + - volumes-provisioner \ No newline at end of file diff --git a/test/EventStore.Client.Tests.Common/docker-compose.cluster.yml b/test/EventStore.Client.Tests.Common/docker-compose.cluster.yml new file mode 100644 index 000000000..2c92d5162 --- /dev/null +++ b/test/EventStore.Client.Tests.Common/docker-compose.cluster.yml @@ -0,0 +1,176 @@ +version: "3.5" + +services: + volumes-provisioner: + image: hasnat/volumes-provisioner + container_name: volumes-provisioner + environment: + PROVISION_DIRECTORIES: "1000:1000:0755:/tmp/certs" + volumes: + - "${ES_CERTS_CLUSTER}:/tmp/certs" + network_mode: none + + cert-gen: + image: eventstore/es-gencert-cli:1.0.2 + container_name: cert-gen + user: "1000:1000" + entrypoint: [ "/bin/sh","-c" ] + # rm -rf /tmp/certs/** + command: + - | + es-gencert-cli create-ca -out /tmp/certs/ca + es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out /tmp/certs/node1 -ip-addresses 127.0.0.1,172.30.240.11 -dns-names localhost + es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out /tmp/certs/node2 -ip-addresses 127.0.0.1,172.30.240.12 -dns-names localhost + es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out /tmp/certs/node3 -ip-addresses 127.0.0.1,172.30.240.13 -dns-names localhost + es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out /tmp/certs/node4 -ip-addresses 127.0.0.1,172.30.240.14 -dns-names localhost + volumes: + - "${ES_CERTS_CLUSTER}:/tmp/certs" + depends_on: + - volumes-provisioner + + seq: + image: datalust/seq:latest + container_name: seq + environment: + ACCEPT_EULA: Y + ports: + - "5341:80" + depends_on: + - volumes-provisioner + - cert-gen + + esdb-node1: + image: ghcr.io/eventstore/eventstore:${ES_DOCKER_TAG} + container_name: esdb-node1 + env_file: + - shared.env + environment: + - EVENTSTORE_GOSSIP_SEED=172.30.240.12:2113,172.30.240.13:2113 + - EVENTSTORE_INT_IP=172.30.240.11 + - EVENTSTORE_CERTIFICATE_FILE=/etc/eventstore/certs/node1/node.crt + - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/etc/eventstore/certs/node1/node.key + - EVENTSTORE_ADVERTISE_HOST_TO_CLIENT_AS=127.0.0.1 + - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2111 + ports: + - "2111:2113" + networks: + clusternetwork: + ipv4_address: 172.30.240.11 + volumes: + - ${ES_CERTS_CLUSTER}:/etc/eventstore/certs + - type: volume + source: eventstore-volume-data1 + target: /var/lib/eventstore + - type: volume + source: eventstore-volume-logs1 + target: /var/log/eventstore + restart: unless-stopped + depends_on: + - cert-gen + + esdb-node2: + image: ghcr.io/eventstore/eventstore:${ES_DOCKER_TAG} + container_name: esdb-node2 + env_file: + - shared.env + environment: + - EVENTSTORE_GOSSIP_SEED=172.30.240.11:2113,172.30.240.13:2113 + - EVENTSTORE_INT_IP=172.30.240.12 + - EVENTSTORE_CERTIFICATE_FILE=/etc/eventstore/certs/node2/node.crt + - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/etc/eventstore/certs/node2/node.key + - EVENTSTORE_ADVERTISE_HOST_TO_CLIENT_AS=127.0.0.1 + - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2112 + ports: + - "2112:2113" + networks: + clusternetwork: + ipv4_address: 172.30.240.12 + volumes: + - ${ES_CERTS_CLUSTER}:/etc/eventstore/certs + - type: volume + source: eventstore-volume-data2 + target: /var/lib/eventstore + - type: volume + source: eventstore-volume-logs2 + target: /var/log/eventstore + restart: unless-stopped + depends_on: + - cert-gen + + esdb-node3: + image: ghcr.io/eventstore/eventstore:${ES_DOCKER_TAG} + container_name: esdb-node3 + env_file: + - shared.env + environment: + - EVENTSTORE_GOSSIP_SEED=172.30.240.11:2113,172.30.240.12:2113 + - EVENTSTORE_INT_IP=172.30.240.13 + - EVENTSTORE_CERTIFICATE_FILE=/etc/eventstore/certs/node3/node.crt + - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/etc/eventstore/certs/node3/node.key + - EVENTSTORE_ADVERTISE_HOST_TO_CLIENT_AS=127.0.0.1 + - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2113 + ports: + - "2113:2113" + networks: + clusternetwork: + ipv4_address: 172.30.240.13 + volumes: + - ${ES_CERTS_CLUSTER}:/etc/eventstore/certs + - type: volume + source: eventstore-volume-data3 + target: /var/lib/eventstore + - type: volume + source: eventstore-volume-logs3 + target: /var/log/eventstore + restart: unless-stopped + depends_on: + - cert-gen + + esdb-node4: + image: ghcr.io/eventstore/eventstore:${ES_DOCKER_TAG} + container_name: esdb-node4 + env_file: + - shared.env + environment: + - EVENTSTORE_READ_ONLY_REPLICA=true + - EVENTSTORE_GOSSIP_SEED=172.30.240.11:2113,172.30.240.12:2113,172.30.240.13:2113 + - EVENTSTORE_INT_IP=172.30.240.14 + - EVENTSTORE_CERTIFICATE_FILE=/etc/eventstore/certs/node4/node.crt + - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/etc/eventstore/certs/node4/node.key + - EVENTSTORE_ADVERTISE_HOST_TO_CLIENT_AS=127.0.0.1 + - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2114 + ports: + - "2114:2113" + networks: + clusternetwork: + ipv4_address: 172.30.240.14 + volumes: + - ${ES_CERTS_CLUSTER}:/etc/eventstore/certs + - type: volume + source: eventstore-volume-data4 + target: /var/lib/eventstore + - type: volume + source: eventstore-volume-logs4 + target: /var/log/eventstore + restart: unless-stopped + depends_on: + - cert-gen + +networks: + clusternetwork: + name: eventstoredb.local + driver: bridge + ipam: + driver: default + config: + - subnet: 172.30.240.0/24 + +volumes: + eventstore-volume-data1: + eventstore-volume-logs1: + eventstore-volume-data2: + eventstore-volume-logs2: + eventstore-volume-data3: + eventstore-volume-logs3: + eventstore-volume-data4: + eventstore-volume-logs4: diff --git a/test/EventStore.Client.Tests.Common/docker-compose.node.yml b/test/EventStore.Client.Tests.Common/docker-compose.node.yml new file mode 100644 index 000000000..a6997819a --- /dev/null +++ b/test/EventStore.Client.Tests.Common/docker-compose.node.yml @@ -0,0 +1,37 @@ +version: "3.5" + +networks: + default: + name: eventstore-network + +services: + + eventstore: + image: ghcr.io/eventstore/eventstore:${ES_DOCKER_TAG} + container_name: eventstore + environment: + - EVENTSTORE_MEM_DB=true + - EVENTSTORE_HTTP_PORT=2113 + - EVENTSTORE_LOG_LEVEL=Information + - EVENTSTORE_RUN_PROJECTIONS=None + - EVENTSTORE_START_STANDARD_PROJECTIONS=true + + # set certificates location + - EVENTSTORE_CERTIFICATE_FILE=/etc/eventstore/certs/node/node.crt + - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/etc/eventstore/certs/node/node.key + - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/etc/eventstore/certs/ca + ports: + - "2113:2113" + volumes: + - ${ES_CERTS_CLUSTER}:/etc/eventstore/certs + - type: volume + source: eventstore-volume-data1 + target: /var/lib/eventstore + - type: volume + source: eventstore-volume-logs1 + target: /var/log/eventstore + restart: unless-stopped + +volumes: + eventstore-volume-data1: + eventstore-volume-logs1: diff --git a/test/EventStore.Client.Tests.Common/shared.env b/test/EventStore.Client.Tests.Common/shared.env index d8746d86a..2b8c93eff 100644 --- a/test/EventStore.Client.Tests.Common/shared.env +++ b/test/EventStore.Client.Tests.Common/shared.env @@ -4,7 +4,7 @@ EVENTSTORE_HTTP_PORT=2113 EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/etc/eventstore/certs/ca EVENTSTORE_DISCOVER_VIA_DNS=false EVENTSTORE_ENABLE_EXTERNAL_TCP=false -EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true +EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=false EVENTSTORE_STREAM_EXISTENCE_FILTER_SIZE=10000 # pass through from environment diff --git a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj index 05e3f6663..0bd9cef57 100644 --- a/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj +++ b/test/EventStore.Client.Tests/EventStore.Client.Tests.csproj @@ -6,18 +6,18 @@ - - - - + + + + - - + + all diff --git a/test/EventStore.Client.UserManagement.Tests/enabling_a_user.cs b/test/EventStore.Client.UserManagement.Tests/enabling_a_user.cs index c57505660..79755e891 100644 --- a/test/EventStore.Client.UserManagement.Tests/enabling_a_user.cs +++ b/test/EventStore.Client.UserManagement.Tests/enabling_a_user.cs @@ -1,7 +1,8 @@ namespace EventStore.Client.Tests; public class enabling_a_user : IClassFixture { - public enabling_a_user(ITestOutputHelper output, InsecureClientTestFixture fixture) => Fixture = fixture.With(x => x.CaptureTestRun(output)); + public enabling_a_user(ITestOutputHelper output, InsecureClientTestFixture fixture) => + Fixture = fixture.With(x => x.CaptureTestRun(output)); InsecureClientTestFixture Fixture { get; }