From 3256c20f227044363782eab73a8343d9190feb0e Mon Sep 17 00:00:00 2001 From: thefringeninja <495495+thefringeninja@users.noreply.github.com> Date: Thu, 27 Jan 2022 23:57:51 +0100 Subject: [PATCH 1/3] build and run samples --- .github/workflows/ci.yml | 33 +++++++++++++++++++ samples/appending-events/Program.cs | 16 +++++++-- samples/persistent-subscriptions/Program.cs | 2 +- samples/quick-start/Program.cs | 21 +++++++----- samples/reading-events/Program.cs | 2 +- samples/secure-with-tls/Program.cs | 2 +- samples/server-side-filtering/Program.cs | 32 +++++++++--------- .../Program.cs | 8 +++-- .../Startup.cs | 2 +- samples/subscribing-to-streams/Program.cs | 20 +++++++---- 10 files changed, 99 insertions(+), 39 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5a45ba4b..e60e79c78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,39 @@ jobs: dotnet restore dotnet list package --vulnerable --include-transitive --framework ${{ matrix.framework }} | tee vulnerabilities.txt ! cat vulnerabilities.txt | grep -q "has the following vulnerable packages" + build-samples: + timeout-minutes: 5 + runs-on: ubuntu-latest + services: + esdb: + image: ghcr.io/eventstore/eventstore:21.10.1-focal # TODO replace with LTS + env: + EVENTSTORE_INSECURE: true + EVENTSTORE_MEMDB: true + EVENTSTORE_RUN_PROJECTIONS: all + EVENTSTORE_START_STANDARD_PROJECTIONS: true + ports: + - 2113:2113 + options: --health-cmd "exit 0" + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install netcoreapp3.1 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.1.x + - name: Install net5.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 5.0.x + - name: Compile + shell: bash + run: | + dotnet build samples + - name: Run + shell: bash + run: | + find samples/ -type f -iname "*.csproj" -print0 | xargs -0L1 dotnet run --project build-dotnet: timeout-minutes: 20 strategy: diff --git a/samples/appending-events/Program.cs b/samples/appending-events/Program.cs index f34a03b8f..f45f5a105 100644 --- a/samples/appending-events/Program.cs +++ b/samples/appending-events/Program.cs @@ -10,10 +10,18 @@ namespace appending_events { class Program { - static void Main(string[] args) { - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://admin:changeit@localhost:2113?TlsVerifyCert=false") + 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; } private static async Task AppendToStream(EventStoreClient client) { @@ -90,6 +98,8 @@ await client.AppendToStreamAsync( } 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, diff --git a/samples/persistent-subscriptions/Program.cs b/samples/persistent-subscriptions/Program.cs index f230b04c3..d3562d41f 100644 --- a/samples/persistent-subscriptions/Program.cs +++ b/samples/persistent-subscriptions/Program.cs @@ -8,7 +8,7 @@ class Program { static async Task Main(string[] args) { await using var client = new EventStorePersistentSubscriptionsClient( - EventStoreClientSettings.Create("esdb://admin:changeit@localhost:2113?TlsVerifyCert=false") + EventStoreClientSettings.Create("esdb://localhost:2113?tls=false") ); await CreatePersistentSubscription(client); await ConnectToPersistentSubscriptionToStream(client); diff --git a/samples/quick-start/Program.cs b/samples/quick-start/Program.cs index 3821c5945..8e9e8f289 100644 --- a/samples/quick-start/Program.cs +++ b/samples/quick-start/Program.cs @@ -17,7 +17,10 @@ public class TestEvent { } class Program { - static async Task Main(string[] args) { + static void Main(string[] args) { + } + + static async Task Samples() { CancellationTokenSource tokenSource = new CancellationTokenSource(); CancellationToken cancellationToken = tokenSource.Token; @@ -61,14 +64,14 @@ await client.AppendToStreamAsync( #endregion overriding-user-credentials #region readStream - var result = client.ReadStreamAsync( - Direction.Forwards, - "some-stream", - StreamPosition.Start, - cancellationToken: cancellationToken); + var result = client.ReadStreamAsync( + Direction.Forwards, + "some-stream", + StreamPosition.Start, + cancellationToken: cancellationToken); - var events = await result.ToListAsync(cancellationToken); - #endregion readStream - } + var events = await result.ToListAsync(cancellationToken); + #endregion readStream + } } } diff --git a/samples/reading-events/Program.cs b/samples/reading-events/Program.cs index ba1b8d5f7..c09c64238 100644 --- a/samples/reading-events/Program.cs +++ b/samples/reading-events/Program.cs @@ -11,7 +11,7 @@ namespace reading_events { class Program { static async Task Main(string[] args) { using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://admin:changeit@localhost:2113?TlsVerifyCert=false") + EventStoreClientSettings.Create("esdb://localhost:2113?tls=false") ); var events = Enumerable.Range(0, 20) diff --git a/samples/secure-with-tls/Program.cs b/samples/secure-with-tls/Program.cs index 3083cb904..b0bdfb605 100644 --- a/samples/secure-with-tls/Program.cs +++ b/samples/secure-with-tls/Program.cs @@ -21,7 +21,7 @@ class Program static async Task Main(string[] args) { // take the address from environment variable (when run with Docker) or use localhost by default - var connectionString = Environment.GetEnvironmentVariable("ESDB_CONNECTION_STRING") ?? "esdb://localhost:2113?Tls=true"; + var connectionString = Environment.GetEnvironmentVariable("ESDB_CONNECTION_STRING") ?? "esdb://localhost:2113?tls=false"; Console.WriteLine($"Connecting to EventStoreDB at: `{connectionString}`"); diff --git a/samples/server-side-filtering/Program.cs b/samples/server-side-filtering/Program.cs index e53c6ecc7..0b9cfc96e 100644 --- a/samples/server-side-filtering/Program.cs +++ b/samples/server-side-filtering/Program.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -11,13 +9,17 @@ namespace server_side_filtering { class Program { static async Task Main() { - using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://localhost:2113?Tls=false") + 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(Position.Start, + await client.SubscribeToAllAsync(SubscriptionPosition.Start, (s, e, c) => { Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); + semaphore.Release(); return Task.CompletedTask; }, filterOptions: new SubscriptionFilterOptions( @@ -29,9 +31,9 @@ await client.SubscribeToAllAsync(Position.Start, }) ); - Thread.Sleep(2000); + await Task.Delay(2000); - for (var i = 0; i < 100; i++) { + for (var i = 0; i < eventCount; i++) { var eventData = new EventData( Uuid.NewUuid(), i % 2 == 0 ? "some-event" : "other-event", @@ -45,12 +47,12 @@ await client.AppendToStreamAsync( ); } - Console.ReadLine(); + await semaphore.WaitAsync(); } private static async Task ExcludeSystemEvents(EventStoreClient client) { #region exclude-system - await client.SubscribeToAllAsync(Position.Start, + await client.SubscribeToAllAsync(SubscriptionPosition.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); @@ -68,7 +70,7 @@ private static async Task EventTypePrefix(EventStoreClient client) { EventTypeFilter.Prefix("customer-")); #endregion event-type-prefix - await client.SubscribeToAllAsync(Position.Start, + await client.SubscribeToAllAsync(SubscriptionPosition.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); @@ -84,7 +86,7 @@ private static async Task EventTypeRegex(EventStoreClient client) { EventTypeFilter.RegularExpression("^user|^company")); #endregion event-type-regex - await client.SubscribeToAllAsync(Position.Start, + await client.SubscribeToAllAsync(SubscriptionPosition.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); @@ -100,7 +102,7 @@ private static async Task StreamPrefix(EventStoreClient client) { StreamFilter.Prefix("user-")); #endregion stream-prefix - await client.SubscribeToAllAsync(Position.Start, + await client.SubscribeToAllAsync(SubscriptionPosition.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); @@ -116,7 +118,7 @@ private static async Task StreamRegex(EventStoreClient client) { StreamFilter.RegularExpression("^account|^savings")); #endregion stream-regex - await client.SubscribeToAllAsync(Position.Start, + await client.SubscribeToAllAsync(SubscriptionPosition.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); @@ -137,7 +139,7 @@ private static async Task CheckpointCallback(EventStoreClient client) { }); #endregion checkpoint - await client.SubscribeToAllAsync(Position.Start, + await client.SubscribeToAllAsync(SubscriptionPosition.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); @@ -159,7 +161,7 @@ private static async Task CheckpointCallbackWithInterval(EventStoreClient client }); #endregion checkpoint-with-interval - await client.SubscribeToAllAsync(Position.Start, + await client.SubscribeToAllAsync(SubscriptionPosition.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); diff --git a/samples/setting-up-dependency-injection/Program.cs b/samples/setting-up-dependency-injection/Program.cs index 7d238212b..f1d6624d7 100644 --- a/samples/setting-up-dependency-injection/Program.cs +++ b/samples/setting-up-dependency-injection/Program.cs @@ -1,10 +1,14 @@ +using System; +using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; namespace setting_up_dependency_injection { public class Program { - public static void Main(string[] args) { - CreateHostBuilder(args).Build().Run(); + 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) => diff --git a/samples/setting-up-dependency-injection/Startup.cs b/samples/setting-up-dependency-injection/Startup.cs index 5c4dda84f..7e28c1d53 100644 --- a/samples/setting-up-dependency-injection/Startup.cs +++ b/samples/setting-up-dependency-injection/Startup.cs @@ -19,7 +19,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); #region setting-up-dependency - services.AddEventStoreClient("esdb://admin:changeit@localhost:2113?TlsVerifyCert=false"); + services.AddEventStoreClient("esdb://admin:changeit@localhost:2113?tls=false"); #endregion setting-up-dependency } diff --git a/samples/subscribing-to-streams/Program.cs b/samples/subscribing-to-streams/Program.cs index 6a9579b83..6286305c1 100644 --- a/samples/subscribing-to-streams/Program.cs +++ b/samples/subscribing-to-streams/Program.cs @@ -9,7 +9,7 @@ namespace subscribing_to_streams { class Program { static async Task Main(string[] args) { using var client = new EventStoreClient( - EventStoreClientSettings.Create("esdb://admin:changeit@localhost:2113?TlsVerifyCert=false") + EventStoreClientSettings.Create("esdb://localhost:2113?tls=false") ); await SubscribeToStream(client); @@ -70,6 +70,7 @@ await client.SubscribeToStreamAsync( private static async Task SubscribeToAll(EventStoreClient client) { #region subscribe-to-all await client.SubscribeToAllAsync( + SubscriptionPosition.Start, async (subscription, evnt, cancellationToken) => { Console.WriteLine($"Received event {evnt.OriginalEventNumber}@{evnt.OriginalStreamId}"); await HandleEvent(evnt); @@ -77,24 +78,29 @@ await client.SubscribeToAllAsync( #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( - new Position(1056, 1056), + SubscriptionPosition.After(result.LogPosition), EventAppeared); #endregion subscribe-to-all-from-position #region subscribe-to-all-live await client.SubscribeToAllAsync( - Position.End, + SubscriptionPosition.Live, EventAppeared); #endregion subscribe-to-all-live #region subscribe-to-all-subscription-dropped - var checkpoint = Position.Start; + var checkpoint = SubscriptionPosition.Start; await client.SubscribeToAllAsync( checkpoint, eventAppeared: async (subscription, evnt, cancellationToken) => { await HandleEvent(evnt); - checkpoint = evnt.OriginalPosition.Value; + checkpoint = SubscriptionPosition.After(evnt.OriginalPosition!.Value); }, subscriptionDropped: ((subscription, reason, exception) => { Console.WriteLine($"Subscription was dropped due to {reason}. {exception}"); @@ -110,6 +116,7 @@ private static async Task SubscribeToFiltered(EventStoreClient client) { #region stream-prefix-filtered-subscription var prefixStreamFilter = new SubscriptionFilterOptions(StreamFilter.Prefix("test-", "other-")); await client.SubscribeToAllAsync( + SubscriptionPosition.Start, EventAppeared, filterOptions: prefixStreamFilter); #endregion stream-prefix-filtered-subscription @@ -122,6 +129,7 @@ await client.SubscribeToAllAsync( private static async Task OverridingUserCredentials(EventStoreClient client) { #region overriding-user-credentials await client.SubscribeToAllAsync( + SubscriptionPosition.Start, EventAppeared, userCredentials: new UserCredentials("admin", "changeit")); #endregion overriding-user-credentials @@ -140,6 +148,6 @@ private static Task HandleEvent(ResolvedEvent evnt) { } private static void Resubscribe(StreamPosition checkpoint) { } - private static void Resubscribe(Position checkpoint) { } + private static void Resubscribe(SubscriptionPosition checkpoint) { } } } From ffa33b46df2d4d73f58a536a6155018035c0f102 Mon Sep 17 00:00:00 2001 From: thefringeninja <495495+thefringeninja@users.noreply.github.com> Date: Thu, 27 Jan 2022 20:06:43 +0100 Subject: [PATCH 2/3] refactor value object tests using idioms --- src/EventStore.Client/Position.cs | 9 +- test/Directory.Build.props | 1 + .../PrefixFilterExpressionTests.cs | 64 ---------- .../RegularFilterExpressionTests.cs | 71 ----------- .../Assertions/ComparableAssertion.cs | 114 ++++++++++++++++++ .../Assertions/EqualityAssertion.cs | 79 ++++++++++++ .../Assertions/NullArgumentAssertion.cs | 46 +++++++ .../Assertions/StringConversionAssertion.cs | 51 ++++++++ .../Assertions/ValueObjectAssertion.cs | 17 +++ .../AutoScenarioDataAttribute.cs | 31 +++++ test/EventStore.Client.Tests/PositionTests.cs | 72 +++-------- .../PrefixFilterExpressionTests.cs | 14 +++ .../RegularFilterExpressionTests.cs | 16 +++ .../StreamPositionTests.cs | 47 +++----- .../StreamRevisionTests.cs | 48 ++------ .../StreamStateTests.cs | 39 +++--- .../EventStore.Client.Tests/TypeExtensions.cs | 50 ++++++++ test/EventStore.Client.Tests/UuidTests.cs | 41 ++----- .../ValueObjectTests.cs | 17 +++ 19 files changed, 512 insertions(+), 315 deletions(-) delete mode 100644 test/EventStore.Client.Streams.Tests/PrefixFilterExpressionTests.cs delete mode 100644 test/EventStore.Client.Streams.Tests/RegularFilterExpressionTests.cs create mode 100644 test/EventStore.Client.Tests/Assertions/ComparableAssertion.cs create mode 100644 test/EventStore.Client.Tests/Assertions/EqualityAssertion.cs create mode 100644 test/EventStore.Client.Tests/Assertions/NullArgumentAssertion.cs create mode 100644 test/EventStore.Client.Tests/Assertions/StringConversionAssertion.cs create mode 100644 test/EventStore.Client.Tests/Assertions/ValueObjectAssertion.cs create mode 100644 test/EventStore.Client.Tests/AutoScenarioDataAttribute.cs create mode 100644 test/EventStore.Client.Tests/PrefixFilterExpressionTests.cs create mode 100644 test/EventStore.Client.Tests/RegularFilterExpressionTests.cs create mode 100644 test/EventStore.Client.Tests/TypeExtensions.cs create mode 100644 test/EventStore.Client.Tests/ValueObjectTests.cs diff --git a/src/EventStore.Client/Position.cs b/src/EventStore.Client/Position.cs index 77dcd6262..eaa541ec1 100644 --- a/src/EventStore.Client/Position.cs +++ b/src/EventStore.Client/Position.cs @@ -6,7 +6,7 @@ namespace EventStore.Client { /// A structure referring to a potential logical record position /// in the Event Store transaction file. /// - public readonly struct Position : IEquatable, IComparable, IPosition { + public readonly struct Position : IEquatable, IComparable, IComparable, IPosition { /// /// Position representing the start of the transaction file /// @@ -111,6 +111,13 @@ public Position(ulong commitPosition, ulong preparePosition) { /// public int CompareTo(Position other) => this == other ? 0 : this > other ? 1 : -1; + + /// + public int CompareTo(object? obj) => obj switch { + null => 1, + Position other => CompareTo(other), + _ => throw new ArgumentException("Object is not a Position"), + }; /// /// Indicates whether this instance and a specified object are equal. diff --git a/test/Directory.Build.props b/test/Directory.Build.props index cb741a073..6bf0189ed 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -4,6 +4,7 @@ true + diff --git a/test/EventStore.Client.Streams.Tests/PrefixFilterExpressionTests.cs b/test/EventStore.Client.Streams.Tests/PrefixFilterExpressionTests.cs deleted file mode 100644 index 963ab2d44..000000000 --- a/test/EventStore.Client.Streams.Tests/PrefixFilterExpressionTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using Xunit; - -namespace EventStore.Client { - public class PrefixFilterExpressionTests { - [Fact] - public void Equality() { - var sut = new PrefixFilterExpression("prefix"); - - Assert.Equal(new PrefixFilterExpression("prefix"), sut); - } - - [Fact] - public void Inequality() { - var sut = new PrefixFilterExpression("prefix"); - - Assert.NotEqual(new PrefixFilterExpression("suffix"), sut); - } - - [Fact] - public void EqualityOperator() { - var sut = new PrefixFilterExpression("prefix"); - - Assert.True(new PrefixFilterExpression("prefix") == sut); - } - - [Fact] - public void InequalityOperator() { - var sut = new PrefixFilterExpression("prefix"); - - Assert.True(new PrefixFilterExpression("suffix") != sut); - } - - [Fact] - public void NullArgument() { - var ex = Assert.Throws(() => new PrefixFilterExpression(null!)); - Assert.Equal("value", ex.ParamName); - } - - [Fact] - public void ExplicitCastToStringReturnsExpectedResult() { - var sut = new PrefixFilterExpression("prefix"); - var result = (string)sut; - - Assert.Equal("prefix", result); - } - - [Fact] - public void ImplicitCastToStringReturnsExpectedResult() { - var sut = new PrefixFilterExpression("prefix"); - string result = sut; - - Assert.Equal("prefix", result); - } - - [Fact] - public void ToStringReturnsExpectedResult() { - var sut = new PrefixFilterExpression("prefix"); - var result = sut.ToString(); - - Assert.Equal("prefix", result); - } - } -} diff --git a/test/EventStore.Client.Streams.Tests/RegularFilterExpressionTests.cs b/test/EventStore.Client.Streams.Tests/RegularFilterExpressionTests.cs deleted file mode 100644 index c3de61c95..000000000 --- a/test/EventStore.Client.Streams.Tests/RegularFilterExpressionTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using Xunit; - -namespace EventStore.Client { - public class RegularFilterExpressionTests { - [Fact] - public void Equality() { - var sut = new RegularFilterExpression("^"); - - Assert.Equal(new RegularFilterExpression("^"), sut); - } - - [Fact] - public void Inequality() { - var sut = new RegularFilterExpression("^"); - - Assert.NotEqual(new RegularFilterExpression("$"), sut); - } - - [Fact] - public void EqualityOperator() { - var sut = new RegularFilterExpression("^"); - - Assert.True(new RegularFilterExpression("^") == sut); - } - - [Fact] - public void InequalityOperator() { - var sut = new RegularFilterExpression("^"); - - Assert.True(new RegularFilterExpression("$") != sut); - } - - [Fact] - public void NullStringArgument() { - var ex = Assert.Throws(() => new RegularFilterExpression((string)null)); - Assert.Equal("value", ex.ParamName); - } - - [Fact] - public void NullRegularExpressionArgument() { - var ex = Assert.Throws(() => new RegularFilterExpression((Regex)null)); - Assert.Equal("value", ex.ParamName); - } - - [Fact] - public void ExplicitCastToStringReturnsExpectedResult() { - var sut = new RegularFilterExpression("^"); - var result = (string)sut; - - Assert.Equal("^", result); - } - - [Fact] - public void ImplicitCastToStringReturnsExpectedResult() { - var sut = new RegularFilterExpression("^"); - string result = sut; - - Assert.Equal("^", result); - } - - [Fact] - public void ToStringReturnsExpectedResult() { - var sut = new RegularFilterExpression("^"); - var result = sut.ToString(); - - Assert.Equal("^", result); - } - } -} diff --git a/test/EventStore.Client.Tests/Assertions/ComparableAssertion.cs b/test/EventStore.Client.Tests/Assertions/ComparableAssertion.cs new file mode 100644 index 000000000..a503cd134 --- /dev/null +++ b/test/EventStore.Client.Tests/Assertions/ComparableAssertion.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using AutoFixture.Idioms; +using AutoFixture.Kernel; +using Xunit; + +// ReSharper disable once CheckNamespace +namespace EventStore.Client { + internal class ComparableAssertion : CompositeIdiomaticAssertion { + public ComparableAssertion(ISpecimenBuilder builder) : base(CreateChildrenAssertions(builder)) { } + + private static IEnumerable CreateChildrenAssertions(ISpecimenBuilder builder) { + yield return new ImplementsIComparableCorrectlyAssertion(); + yield return new SameValueComparableAssertion(builder); + yield return new DifferentValueComparableAssertion(builder); + } + + private class ImplementsIComparableCorrectlyAssertion : IdiomaticAssertion { + public override void Verify(Type type) => + Assert.False(type.ImplementsGenericIComparable() && !type.ImplementsIComparable(), + $"The type {type} implemented IComparable without implementing IComparable."); + } + + private class SameValueComparableAssertion : IdiomaticAssertion { + private readonly ISpecimenBuilder _builder; + + public SameValueComparableAssertion(ISpecimenBuilder builder) => _builder = builder; + + public override void Verify(Type type) { + if (!type.ImplementsGenericIComparable() || !type.ImplementsIComparable()) { + return; + } + + var context = new SpecimenContext(_builder); + + var instance = context.Resolve(type); + + Assert.True(type.InvokeGreaterThanOrEqualOperator(instance, instance), + $"The type {type} did not implement >= correctly, should be true for the same instance."); + Assert.False(type.InvokeGreaterThanOperator(instance, instance), + $"The type {type} did not implement > correctly, should be false for the same instance."); + Assert.True(type.InvokeLessThanOrEqualOperator(instance, instance), + $"The type {type} did not implement <= correctly, should be true for the same instance."); + + Assert.False(type.InvokeLessThanOperator(instance, instance), + $"The type {type} did not implement <= correctly, should be true for the same instance."); + + if (type.ImplementsGenericIComparable()) { + Assert.Equal(0, type.InvokeGenericCompareTo(instance, instance)); + } + + if (type.ImplementsIComparable()) { + Assert.Equal(0, type.InvokeCompareTo(instance, instance)); + } + } + } + + private class DifferentValueComparableAssertion : IdiomaticAssertion { + private readonly ISpecimenBuilder _builder; + + public DifferentValueComparableAssertion(ISpecimenBuilder builder) => + _builder = builder ?? throw new ArgumentNullException(nameof(builder)); + + public override void Verify(Type type) { + if (!type.ImplementsGenericIComparable() || !type.ImplementsIComparable()) { + return; + } + + var context = new SpecimenContext(_builder); + + var instance = context.Resolve(type); + var other = context.Resolve(type); + + var compareToGeneric = type.InvokeGenericCompareTo(instance, other); + Assert.NotEqual(0, compareToGeneric); + + var compareTo = type.InvokeCompareTo(instance, other); + Assert.Equal(compareToGeneric, compareTo); + + Assert.Equal(1, type.InvokeCompareTo(instance, null)); + + var ex = Assert.Throws(() => { + try { + type.InvokeCompareTo(instance, new object()); + } catch (TargetInvocationException ex) { + throw ex.InnerException!; + } + }); + Assert.Equal("Object is not a " + type.Name, ex.Message); + + if (compareToGeneric < 0) { + Assert.False(type.InvokeGreaterThanOrEqualOperator(instance, other), + $"The type {type} did not implement >= correctly, should be false for different instances."); + Assert.False(type.InvokeGreaterThanOperator(instance, other), + $"The type {type} did not implement > correctly, should be false for different instances."); + Assert.True(type.InvokeLessThanOrEqualOperator(instance, other), + $"The type {type} did not implement <= correctly, should be true for different instances."); + Assert.True(type.InvokeLessThanOperator(instance, other), + $"The type {type} did not implement <= correctly, should be true for different instances."); + } else { + Assert.True(type.InvokeGreaterThanOrEqualOperator(instance, other), + $"The type {type} did not implement >= correctly, should be true for different instances."); + Assert.True(type.InvokeGreaterThanOperator(instance, other), + $"The type {type} did not implement > correctly, should be true for different instances."); + Assert.False(type.InvokeLessThanOrEqualOperator(instance, other), + $"The type {type} did not implement <= correctly, should be false for different instances."); + Assert.False(type.InvokeLessThanOperator(instance, other), + $"The type {type} did not implement <= correctly, should be false for different instances."); + } + } + } + } +} diff --git a/test/EventStore.Client.Tests/Assertions/EqualityAssertion.cs b/test/EventStore.Client.Tests/Assertions/EqualityAssertion.cs new file mode 100644 index 000000000..82782c42a --- /dev/null +++ b/test/EventStore.Client.Tests/Assertions/EqualityAssertion.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using AutoFixture.Idioms; +using AutoFixture.Kernel; + +// ReSharper disable once CheckNamespace +namespace EventStore.Client { + internal class EqualityAssertion : CompositeIdiomaticAssertion { + public EqualityAssertion(ISpecimenBuilder builder) : base(CreateChildrenAssertions(builder)) { } + + private static IEnumerable CreateChildrenAssertions(ISpecimenBuilder builder) { + yield return new EqualsNewObjectAssertion(builder); + yield return new EqualsSelfAssertion(builder); + yield return new EqualsSuccessiveAssertion(builder); + yield return new GetHashCodeSuccessiveAssertion(builder); + yield return new SameValueEqualityOperatorsAssertion(builder); + yield return new DifferentValuesEqualityOperatorsAssertion(builder); + } + + private class SameValueEqualityOperatorsAssertion : IdiomaticAssertion { + private readonly ISpecimenBuilder _builder; + + public SameValueEqualityOperatorsAssertion(ISpecimenBuilder builder) => + _builder = builder ?? throw new ArgumentNullException(nameof(builder)); + + public override void Verify(Type type) { + if (type == null) throw new ArgumentNullException(nameof(type)); + var instance = new SpecimenContext(_builder).Resolve(type); + + var equals = type.InvokeEqualityOperator(instance, instance); + var notEquals = type.InvokeInequalityOperator(instance, instance); + + if (equals == notEquals) { + throw new Exception( + $"The type '{type}' returned {equals} for both equality (==) and inequality (!=)."); + } + + if (!equals) { + throw new Exception($"The type '{type}' did not implement the equality (==) operator correctly."); + } + + if (notEquals) { + throw new Exception($"The type '{type}' did not implement the inequality (!=) operator correctly."); + } + } + } + + private class DifferentValuesEqualityOperatorsAssertion : IdiomaticAssertion { + private readonly ISpecimenBuilder _builder; + + public DifferentValuesEqualityOperatorsAssertion(ISpecimenBuilder builder) => + _builder = builder ?? throw new ArgumentNullException(nameof(builder)); + + public override void Verify(Type type) { + if (type == null) throw new ArgumentNullException(nameof(type)); + var context = new SpecimenContext(_builder); + var instance = context.Resolve(type); + var other = context.Resolve(type); + + var equals = type.InvokeEqualityOperator(instance, other); + var notEquals = type.InvokeInequalityOperator(instance, other); + + if (equals == notEquals) { + throw new Exception( + $"The type '{type}' returned {equals} for both equality (==) and inequality (!=)."); + } + + if (equals) { + throw new Exception($"The type '{type}' did not implement the equality (==) operator correctly."); + } + + if (!notEquals) { + throw new Exception($"The type '{type}' did not implement the inequality (!=) operator correctly."); + } + + } + } + } +} diff --git a/test/EventStore.Client.Tests/Assertions/NullArgumentAssertion.cs b/test/EventStore.Client.Tests/Assertions/NullArgumentAssertion.cs new file mode 100644 index 000000000..7d88f1798 --- /dev/null +++ b/test/EventStore.Client.Tests/Assertions/NullArgumentAssertion.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; +using System.Reflection; +using AutoFixture.Idioms; +using AutoFixture.Kernel; +using Xunit; + +// ReSharper disable once CheckNamespace +namespace EventStore.Client { + internal class NullArgumentAssertion : IdiomaticAssertion { + private readonly ISpecimenBuilder _builder; + + public NullArgumentAssertion(ISpecimenBuilder builder) => _builder = builder; + + public override void Verify(Type type) { + var context = new SpecimenContext(_builder); + + Assert.All(type.GetConstructors(), constructor => { + var parameters = constructor.GetParameters(); + + Assert.All(parameters.Where(p => p.ParameterType.IsClass || + p.ParameterType == typeof(string) || + p.ParameterType.IsGenericType && + p.ParameterType.GetGenericArguments().FirstOrDefault() == + typeof(Nullable<>)), p => { + var args = new object[parameters.Length]; + + for (var i = 0; i < args.Length; i++) { + if (i != p.Position) { + args[i] = context.Resolve(p.ParameterType); + } + } + + var ex = Assert.Throws(() => { + try { + constructor.Invoke(args); + } catch (TargetInvocationException ex) { + throw ex.InnerException!; + } + }); + Assert.Equal(p.Name, ex.ParamName); + }); + }); + } + } +} diff --git a/test/EventStore.Client.Tests/Assertions/StringConversionAssertion.cs b/test/EventStore.Client.Tests/Assertions/StringConversionAssertion.cs new file mode 100644 index 000000000..e42f19c27 --- /dev/null +++ b/test/EventStore.Client.Tests/Assertions/StringConversionAssertion.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using AutoFixture.Idioms; +using AutoFixture.Kernel; +using Xunit; + +// ReSharper disable once CheckNamespace +namespace EventStore.Client { + internal class StringConversionAssertion : IdiomaticAssertion { + private readonly ISpecimenBuilder _builder; + + public StringConversionAssertion(ISpecimenBuilder builder) => _builder = builder; + + public override void Verify(Type type) { + var context = new SpecimenContext(_builder); + + var constructor = type.GetConstructor(new[] {typeof(string)}); + + if (constructor is null) { + return; + } + + var value = (string)context.Resolve(typeof(string)); + var instance = constructor.Invoke(new object[] {value}); + var args =new[]{instance}; + + var @explicit = type + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(m => m.Name == "op_Explicit" && m.ReturnType == typeof(string)); + if (@explicit is not null) { + Assert.Equal(value, @explicit.Invoke(null, args)); + } + + var @implicit = type + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == typeof(string)); + if (@implicit is not null) { + Assert.Equal(value, @implicit.Invoke(null, args)); + } + + var toString = type + .GetMethods(BindingFlags.Public | BindingFlags.Public) + .FirstOrDefault(m => m.Name == "ToString" && m.ReturnType == typeof(string)); + if (toString is not null) { + Assert.Equal(value, toString.Invoke(instance, null)); + } + } + } +} diff --git a/test/EventStore.Client.Tests/Assertions/ValueObjectAssertion.cs b/test/EventStore.Client.Tests/Assertions/ValueObjectAssertion.cs new file mode 100644 index 000000000..847f1f6e6 --- /dev/null +++ b/test/EventStore.Client.Tests/Assertions/ValueObjectAssertion.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using AutoFixture.Idioms; +using AutoFixture.Kernel; + +// ReSharper disable once CheckNamespace +namespace EventStore.Client { + internal class ValueObjectAssertion : CompositeIdiomaticAssertion { + public ValueObjectAssertion(ISpecimenBuilder builder) : base(CreateChildrenAssertions(builder)) { } + + private static IEnumerable CreateChildrenAssertions(ISpecimenBuilder builder) { + yield return new EqualityAssertion(builder); + yield return new ComparableAssertion(builder); + yield return new StringConversionAssertion(builder); + yield return new NullArgumentAssertion(builder); + } + } +} diff --git a/test/EventStore.Client.Tests/AutoScenarioDataAttribute.cs b/test/EventStore.Client.Tests/AutoScenarioDataAttribute.cs new file mode 100644 index 000000000..634075eed --- /dev/null +++ b/test/EventStore.Client.Tests/AutoScenarioDataAttribute.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using AutoFixture; +using AutoFixture.Xunit2; +using Xunit.Sdk; + +namespace EventStore.Client { + [DataDiscoverer("AutoFixture.Xunit2.NoPreDiscoveryDataDiscoverer", "AutoFixture.Xunit2")] + public class AutoScenarioDataAttribute : DataAttribute { + private readonly Type _fixtureType; + public int Iterations { get; } + + public AutoScenarioDataAttribute(Type fixtureType, int iterations = 3) { + _fixtureType = fixtureType; + Iterations = iterations; + } + + public override IEnumerable GetData(MethodInfo testMethod) { + var customAutoData = new CustomAutoData(_fixtureType); + + return Enumerable.Range(0, Iterations).SelectMany(_ => customAutoData.GetData(testMethod)); + } + + private class CustomAutoData : AutoDataAttribute { + public CustomAutoData(Type fixtureType) : base(() => (IFixture)Activator.CreateInstance(fixtureType)) { + } + } + } +} diff --git a/test/EventStore.Client.Tests/PositionTests.cs b/test/EventStore.Client.Tests/PositionTests.cs index d198f52ee..f97ab826a 100644 --- a/test/EventStore.Client.Tests/PositionTests.cs +++ b/test/EventStore.Client.Tests/PositionTests.cs @@ -1,36 +1,28 @@ using System; using System.Collections.Generic; using System.Linq; +using AutoFixture; using Xunit; namespace EventStore.Client { - public class PositionTests { - [Fact] - public void Equality() { - var sut = new Position(6, 5); - - Assert.Equal(new Position(6, 5), sut); + public class PositionTests : ValueObjectTests { + public PositionTests() : base(new ScenarioFixture()) { } [Fact] - public void Inequality() { - var sut = new Position(6, 5); + public void IsComparable() => + Assert.IsAssignableFrom>(_fixture.Create()); - Assert.NotEqual(new Position(7, 6), sut); - } + [Theory, AutoScenarioData(typeof(ScenarioFixture))] + public void StartIsLessThanAll(Position other) => Assert.True(Position.Start < other); - [Fact] - public void EqualityOperator() { - var sut = new Position(6, 5); - - Assert.True(new Position(6, 5) == sut); - } + [Theory, AutoScenarioData(typeof(ScenarioFixture))] + public void LiveIsGreaterThanAll(Position other) => Assert.True(Position.End > other); [Fact] - public void InequalityOperator() { - var sut = new Position(6, 5); - - Assert.True(new Position(7, 6) != sut); + public void ToStringReturnsExpectedResult() { + var sut = _fixture.Create(); + Assert.Equal($"C:{sut.CommitPosition}/P:{sut.PreparePosition}", sut.ToString()); } public static IEnumerable ArgumentOutOfRangeTestCases() { @@ -49,42 +41,10 @@ public void ArgumentOutOfRange(ulong commitPosition, ulong preparePosition, stri Assert.Equal(name, ex.ParamName); } - public static IEnumerable GreaterThanTestCases() { - yield return new object[] {new Position(6, 6), new Position(6, 5)}; - yield return new object[] {Position.End, new Position(ulong.MaxValue, 0),}; - } - - [Theory, MemberData(nameof(GreaterThanTestCases))] - public void GreaterThan(Position left, Position right) => Assert.True(left > right); - - public static IEnumerable GreaterThanOrEqualToTestCases() - => GreaterThanTestCases().Concat(new[] {new object[] {Position.Start, Position.Start}}); - - [Theory, MemberData(nameof(GreaterThanOrEqualToTestCases))] - public void GreaterThanOrEqualTo(Position left, Position right) => Assert.True(left >= right); - - public static IEnumerable LessThanTestCases() { - yield return new object[] {new Position(6, 5), new Position(6, 6)}; - yield return new object[] {new Position(ulong.MaxValue, 0), Position.End,}; + private class ScenarioFixture : Fixture { + public ScenarioFixture() { + Customize(composer => composer.FromFactory(value => new Position(value, value))); + } } - - [Theory, MemberData(nameof(LessThanTestCases))] - public void LessThan(Position left, Position right) => Assert.True(left < right); - - public static IEnumerable LessThanOrEqualToTestCases() - => LessThanTestCases().Concat(new[] {new object[] {Position.End, Position.End}}); - - [Theory, MemberData(nameof(LessThanOrEqualToTestCases))] - public void LessThanOrEqualTo(Position left, Position right) => Assert.True(left <= right); - - public static IEnumerable CompareToTestCases() { - yield return new object[] {new Position(6, 5), new Position(6, 6), -1}; - yield return new object[] {new Position(6, 6), new Position(6, 5), 1}; - yield return new object[] {new Position(6, 6), new Position(6, 6), 0}; - } - - [Theory, MemberData(nameof(CompareToTestCases))] - public void CompareTo(Position left, Position right, int expected) => - Assert.Equal(expected, left.CompareTo(right)); } } diff --git a/test/EventStore.Client.Tests/PrefixFilterExpressionTests.cs b/test/EventStore.Client.Tests/PrefixFilterExpressionTests.cs new file mode 100644 index 000000000..a5d521101 --- /dev/null +++ b/test/EventStore.Client.Tests/PrefixFilterExpressionTests.cs @@ -0,0 +1,14 @@ +using AutoFixture; + +namespace EventStore.Client { + public class PrefixFilterExpressionTests : ValueObjectTests { + public PrefixFilterExpressionTests() : base(new ScenarioFixture()) { } + + private class ScenarioFixture : Fixture { + public ScenarioFixture() { + Customize(composer => + composer.FromFactory(value => new PrefixFilterExpression(value))); + } + } + } +} diff --git a/test/EventStore.Client.Tests/RegularFilterExpressionTests.cs b/test/EventStore.Client.Tests/RegularFilterExpressionTests.cs new file mode 100644 index 000000000..df7cfd402 --- /dev/null +++ b/test/EventStore.Client.Tests/RegularFilterExpressionTests.cs @@ -0,0 +1,16 @@ +using System.Text.RegularExpressions; +using AutoFixture; + +namespace EventStore.Client { + public class RegularFilterExpressionTests : ValueObjectTests { + public RegularFilterExpressionTests() : base(new ScenarioFixture()) { + } + + private class ScenarioFixture : Fixture { + public ScenarioFixture() { + Customize(composer => + composer.FromFactory(value => new RegularFilterExpression(value))); + } + } + } +} diff --git a/test/EventStore.Client.Tests/StreamPositionTests.cs b/test/EventStore.Client.Tests/StreamPositionTests.cs index 5ad94ca44..dd55e5bc7 100644 --- a/test/EventStore.Client.Tests/StreamPositionTests.cs +++ b/test/EventStore.Client.Tests/StreamPositionTests.cs @@ -1,32 +1,16 @@ using System; using System.Collections.Generic; +using AutoFixture; using Xunit; namespace EventStore.Client { - public class StreamPositionTests { - [Fact] - public void Equality() { - var sut = new StreamPosition(1); - Assert.Equal(new StreamPosition(1), sut); - } - - [Fact] - public void Inequality() { - var sut = new StreamPosition(1); - Assert.NotEqual(new StreamPosition(2), sut); - } - - [Fact] - public void EqualityOperator() { - var sut = new StreamPosition(1); - Assert.True(new StreamPosition(1) == sut); + public class StreamPositionTests : ValueObjectTests { + public StreamPositionTests() : base(new ScenarioFixture()) { } [Fact] - public void InequalityOperator() { - var sut = new StreamPosition(1); - Assert.True(new StreamPosition(2) != sut); - } + public void IsComparable() => + Assert.IsAssignableFrom>(_fixture.Create()); [Fact] public void AdditionOperator() { @@ -93,25 +77,15 @@ public void FromStreamPositionReturnsExpectedResult() { Assert.Equal(new StreamPosition(0), result); } - public static IEnumerable ComparableTestCases() { - yield return new object[] {StreamPosition.Start, StreamPosition.Start, 0}; - yield return new object[] {StreamPosition.Start, StreamPosition.End, -1}; - yield return new object[] {StreamPosition.End, StreamPosition.Start, 1}; - } - - [Theory, MemberData(nameof(ComparableTestCases))] - public void Comparability(StreamPosition left, StreamPosition right, int expected) - => Assert.Equal(expected, left.CompareTo(right)); - [Fact] - public void ExplicitConversionFromStreamPositionReturnsExpectedResult() { + public void ExplicitConversionToUInt64ReturnsExpectedResult() { const ulong value = 0UL; var actual = (ulong)new StreamPosition(value); Assert.Equal(value, actual); } [Fact] - public void ImplicitConversionFromStreamPositionReturnsExpectedResult() { + public void ImplicitConversionToUInt64ReturnsExpectedResult() { const ulong value = 0UL; ulong actual = new StreamPosition(value); Assert.Equal(value, actual); @@ -146,5 +120,12 @@ public void ToUInt64ExpectedResult() { Assert.Equal(expected, new StreamPosition(expected).ToUInt64()); } + + private class ScenarioFixture : Fixture { + public ScenarioFixture() { + Customize(composer => composer.FromFactory(value => new StreamPosition(value))); + } + } + } } diff --git a/test/EventStore.Client.Tests/StreamRevisionTests.cs b/test/EventStore.Client.Tests/StreamRevisionTests.cs index a50cba3a4..4faa865cb 100644 --- a/test/EventStore.Client.Tests/StreamRevisionTests.cs +++ b/test/EventStore.Client.Tests/StreamRevisionTests.cs @@ -1,32 +1,15 @@ using System; using System.Collections.Generic; +using AutoFixture; using Xunit; namespace EventStore.Client { - public class StreamRevisionTests { - [Fact] - public void Equality() { - var sut = new StreamRevision(1); - Assert.Equal(new StreamRevision(1), sut); - } - - [Fact] - public void Inequality() { - var sut = new StreamRevision(1); - Assert.NotEqual(new StreamRevision(2), sut); - } - - [Fact] - public void EqualityOperator() { - var sut = new StreamRevision(1); - Assert.True(new StreamRevision(1) == sut); - } + public class StreamRevisionTests : ValueObjectTests { + public StreamRevisionTests() : base(new ScenarioFixture()) { } [Fact] - public void InequalityOperator() { - var sut = new StreamRevision(1); - Assert.True(new StreamRevision(2) != sut); - } + public void IsComparable() => + Assert.IsAssignableFrom>(_fixture.Create()); [Fact] public void AdditionOperator() { @@ -92,26 +75,15 @@ public void FromStreamPositionReturnsExpectedResult() { Assert.Equal(new StreamRevision(0), result); } - public static IEnumerable ComparableTestCases() { - var start = new StreamRevision(0); - yield return new object[] {start, start, 0}; - yield return new object[] {start, StreamRevision.None, -1}; - yield return new object[] {StreamRevision.None, start, 1}; - } - - [Theory, MemberData(nameof(ComparableTestCases))] - public void Comparability(StreamRevision left, StreamRevision right, int expected) - => Assert.Equal(expected, left.CompareTo(right)); - [Fact] - public void ExplicitConversionFromStreamRevisionReturnsExpectedResult() { + public void ExplicitConversionToUInt64ReturnsExpectedResult() { const ulong value = 0UL; var actual = (ulong)new StreamRevision(value); Assert.Equal(value, actual); } [Fact] - public void ImplicitConversionFromStreamRevisionReturnsExpectedResult() { + public void ImplicitConversionToUInt64ReturnsExpectedResult() { const ulong value = 0UL; ulong actual = new StreamRevision(value); Assert.Equal(value, actual); @@ -146,5 +118,11 @@ public void ToUInt64ExpectedResult() { Assert.Equal(expected, new StreamRevision(expected).ToUInt64()); } + + private class ScenarioFixture : Fixture { + public ScenarioFixture() { + Customize(composer => composer.FromFactory(value => new StreamRevision(value))); + } + } } } diff --git a/test/EventStore.Client.Tests/StreamStateTests.cs b/test/EventStore.Client.Tests/StreamStateTests.cs index 485150b4f..c43cc3966 100644 --- a/test/EventStore.Client.Tests/StreamStateTests.cs +++ b/test/EventStore.Client.Tests/StreamStateTests.cs @@ -1,31 +1,14 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using AutoFixture; using Xunit; namespace EventStore.Client { - public class StreamStateTests { - [Fact] - public void Equality() { - var sut = StreamState.NoStream; - Assert.Equal(StreamState.NoStream, sut); - } - - [Fact] - public void Inequality() { - var sut = StreamState.NoStream; - Assert.NotEqual(StreamState.Any, sut); - } - - [Fact] - public void EqualityOperator() { - var sut = StreamState.NoStream; - Assert.True(StreamState.NoStream == sut); - } - - [Fact] - public void InequalityOperator() { - var sut = StreamState.NoStream; - Assert.True(StreamState.Any != sut); + public class StreamStateTests : ValueObjectTests { + public StreamStateTests() : base(new ScenarioFixture()) { } public static IEnumerable ArgumentOutOfRangeTestCases() { @@ -63,5 +46,15 @@ public static IEnumerable ToStringTestCases() { public void ToStringExpectedResult(StreamState sut, string expected) { Assert.Equal(expected, sut.ToString()); } + + private class ScenarioFixture : Fixture { + private static int RefCount; + + private static readonly StreamState[] Instances = Array.ConvertAll(typeof(StreamState) + .GetFields(BindingFlags.Public | BindingFlags.Static), fi => (StreamState)fi.GetValue(null)!); + + public ScenarioFixture() => Customize(composer => + composer.FromFactory(() => Instances[Interlocked.Increment(ref RefCount) % Instances.Length])); + } } } diff --git a/test/EventStore.Client.Tests/TypeExtensions.cs b/test/EventStore.Client.Tests/TypeExtensions.cs new file mode 100644 index 000000000..c8469c6d6 --- /dev/null +++ b/test/EventStore.Client.Tests/TypeExtensions.cs @@ -0,0 +1,50 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace EventStore.Client { + internal static class TypeExtensions { + public static bool InvokeEqualityOperator(this Type type, object left, object right) => + type.InvokeOperator("Equality", left, right); + + public static bool InvokeInequalityOperator(this Type type, object left, object right) => + type.InvokeOperator("Inequality", left, right); + + public static bool InvokeGreaterThanOperator(this Type type, object left, object right) => + type.InvokeOperator("GreaterThan", left, right); + + public static bool InvokeLessThanOperator(this Type type, object left, object right) => + type.InvokeOperator("LessThan", left, right); + + public static bool InvokeGreaterThanOrEqualOperator(this Type type, object left, object right) => + type.InvokeOperator("GreaterThanOrEqual", left, right); + + public static bool InvokeLessThanOrEqualOperator(this Type type, object left, object right) => + type.InvokeOperator("LessThanOrEqual", left, right); + + public static int InvokeGenericCompareTo(this Type type, object left, object right) => + (int)typeof(IComparable<>).MakeGenericType(type) + .GetMethod("CompareTo", BindingFlags.Public | BindingFlags.Instance)! + .Invoke(left, new[] {right})!; + + public static int InvokeCompareTo(this Type type, object left, object right) => + (int)typeof(IComparable) + .GetMethod("CompareTo", BindingFlags.Public | BindingFlags.Instance)! + .Invoke(left, new[] {right})!; + + public static bool ImplementsGenericIComparable(this Type type) => + type.GetInterfaces().Any(t => t == typeof(IComparable<>).MakeGenericType(type)); + + public static bool ImplementsIComparable(this Type type) => + type.GetInterfaces().Length > 0 && type.GetInterfaces().Any(t => t == typeof(IComparable)); + + private static bool InvokeOperator(this Type type, string name, object left, object right) { + var op = type.GetMethod($"op_{name}", BindingFlags.Public | BindingFlags.Static); + if (op == null) { + throw new Exception($"The type {type} did not implement op_{name}."); + } + + return (bool)op.Invoke(null, new[] {left, right})!; + } + } +} diff --git a/test/EventStore.Client.Tests/UuidTests.cs b/test/EventStore.Client.Tests/UuidTests.cs index 209f7214e..4ef2c1c90 100644 --- a/test/EventStore.Client.Tests/UuidTests.cs +++ b/test/EventStore.Client.Tests/UuidTests.cs @@ -1,36 +1,10 @@ using System; +using AutoFixture; using Xunit; namespace EventStore.Client { - public class UuidTests { - [Fact] - public void Equality() { - var sut = Uuid.NewUuid(); - Assert.Equal(Uuid.FromGuid(sut.ToGuid()), sut); - } - - [Fact] - public void Inequality() { - var sut = Uuid.NewUuid(); - Assert.NotEqual(Uuid.NewUuid(), sut); - } - - [Fact] - public void EqualityOperator() { - var sut = Uuid.NewUuid(); - Assert.True(Uuid.FromGuid(sut.ToGuid()) == sut); - } - - [Fact] - public void InequalityOperator() { - var sut = Uuid.NewUuid(); - Assert.True(Uuid.NewUuid() != sut); - } - - [Fact] - public void ArgumentNullException() { - var ex = Assert.Throws(() => Uuid.Parse(null!)); - Assert.Equal("value", ex.ParamName); + public class UuidTests : ValueObjectTests { + public UuidTests() : base(new ScenarioFixture()) { } [Fact] @@ -45,17 +19,16 @@ public void ToGuidReturnsExpectedResult() { public void ToStringProducesExpectedResult() { var sut = Uuid.NewUuid(); - Assert.Equal(sut.ToGuid().ToString(),sut.ToString()); + Assert.Equal(sut.ToGuid().ToString(), sut.ToString()); } [Fact] public void ToFormattedStringProducesExpectedResult() { var sut = Uuid.NewUuid(); - Assert.Equal(sut.ToGuid().ToString("n"),sut.ToString("n")); + Assert.Equal(sut.ToGuid().ToString("n"), sut.ToString("n")); } - [Fact] public void ToDtoReturnsExpectedResult() { var msb = GetRandomInt64(); @@ -95,5 +68,9 @@ private static long GetRandomInt64() { return BitConverter.ToInt64(buffer, 0); } + + private class ScenarioFixture : Fixture { + public ScenarioFixture() => Customize(composer => composer.FromFactory(Uuid.FromGuid)); + } } } diff --git a/test/EventStore.Client.Tests/ValueObjectTests.cs b/test/EventStore.Client.Tests/ValueObjectTests.cs new file mode 100644 index 000000000..165aed0e2 --- /dev/null +++ b/test/EventStore.Client.Tests/ValueObjectTests.cs @@ -0,0 +1,17 @@ +using System; +using AutoFixture; +using Xunit; + +namespace EventStore.Client { + public abstract class ValueObjectTests { + protected readonly Fixture _fixture; + + protected ValueObjectTests(Fixture fixture) => _fixture = fixture; + + [Fact] + public void ValueObjectIsWellBehaved() => _fixture.Create().Verify(typeof(T)); + + [Fact] + public void ValueObjectIsEquatable() => Assert.IsAssignableFrom>(_fixture.Create()); + } +} From 010f3eeb597a9ce4e3f0ecd02bff525f37e433d6 Mon Sep 17 00:00:00 2001 From: thefringeninja <495495+thefringeninja@users.noreply.github.com> Date: Thu, 27 Jan 2022 20:24:34 +0100 Subject: [PATCH 3/3] implement FromStream / FromAll --- samples/server-side-filtering/Program.cs | 16 +- samples/subscribing-to-streams/Program.cs | 39 +++-- .../EventStoreClient.Subscriptions.cs | 151 ++---------------- .../Streams/ReadReq.cs | 33 +++- src/EventStore.Client/FromAll.cs | 101 ++++++++++++ src/EventStore.Client/FromStream.cs | 101 ++++++++++++ .../update_existing_with_check_point.cs | 19 ++- ...date_existing_with_check_point_filtered.cs | 12 +- .../when_writing_and_filtering_out_events.cs | 21 +-- .../update_existing_with_check_point.cs | 19 +-- .../Bugs/Issue_104.cs | 21 +-- .../Bugs/Issue_2544.cs | 14 +- .../Security/SecurityFixture.cs | 50 +++--- .../subscribe_resolve_link_to.cs | 22 +-- .../subscribe_to_all.cs | 16 +- .../subscribe_to_all_filtered.cs | 12 +- .../subscribe_to_all_filtered_live.cs | 7 +- ...subscribe_to_all_filtered_with_position.cs | 7 +- .../subscribe_to_all_live.cs | 8 +- .../subscribe_to_all_with_position.cs | 13 +- .../subscribe_to_stream.cs | 24 ++- .../subscribe_to_stream_live.cs | 16 +- .../subscribe_to_stream_with_revision.cs | 22 ++- test/EventStore.Client.Tests/FromAllTests.cs | 52 ++++++ .../FromStreamTests.cs | 54 +++++++ 25 files changed, 541 insertions(+), 309 deletions(-) create mode 100644 src/EventStore.Client/FromAll.cs create mode 100644 src/EventStore.Client/FromStream.cs create mode 100644 test/EventStore.Client.Tests/FromAllTests.cs create mode 100644 test/EventStore.Client.Tests/FromStreamTests.cs diff --git a/samples/server-side-filtering/Program.cs b/samples/server-side-filtering/Program.cs index 0b9cfc96e..e2e2c814e 100644 --- a/samples/server-side-filtering/Program.cs +++ b/samples/server-side-filtering/Program.cs @@ -16,7 +16,7 @@ static async Task Main() { EventStoreClientSettings.Create("esdb://localhost:2113?tls=false") ); - await client.SubscribeToAllAsync(SubscriptionPosition.Start, + await client.SubscribeToAllAsync(FromAll.Start, (s, e, c) => { Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); semaphore.Release(); @@ -52,7 +52,7 @@ await client.AppendToStreamAsync( private static async Task ExcludeSystemEvents(EventStoreClient client) { #region exclude-system - await client.SubscribeToAllAsync(SubscriptionPosition.Start, + await client.SubscribeToAllAsync(FromAll.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); @@ -70,7 +70,7 @@ private static async Task EventTypePrefix(EventStoreClient client) { EventTypeFilter.Prefix("customer-")); #endregion event-type-prefix - await client.SubscribeToAllAsync(SubscriptionPosition.Start, + await client.SubscribeToAllAsync(FromAll.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); @@ -86,7 +86,7 @@ private static async Task EventTypeRegex(EventStoreClient client) { EventTypeFilter.RegularExpression("^user|^company")); #endregion event-type-regex - await client.SubscribeToAllAsync(SubscriptionPosition.Start, + await client.SubscribeToAllAsync(FromAll.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); @@ -102,7 +102,7 @@ private static async Task StreamPrefix(EventStoreClient client) { StreamFilter.Prefix("user-")); #endregion stream-prefix - await client.SubscribeToAllAsync(SubscriptionPosition.Start, + await client.SubscribeToAllAsync(FromAll.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); @@ -118,7 +118,7 @@ private static async Task StreamRegex(EventStoreClient client) { StreamFilter.RegularExpression("^account|^savings")); #endregion stream-regex - await client.SubscribeToAllAsync(SubscriptionPosition.Start, + await client.SubscribeToAllAsync(FromAll.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); @@ -139,7 +139,7 @@ private static async Task CheckpointCallback(EventStoreClient client) { }); #endregion checkpoint - await client.SubscribeToAllAsync(SubscriptionPosition.Start, + await client.SubscribeToAllAsync(FromAll.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); @@ -161,7 +161,7 @@ private static async Task CheckpointCallbackWithInterval(EventStoreClient client }); #endregion checkpoint-with-interval - await client.SubscribeToAllAsync(SubscriptionPosition.Start, + await client.SubscribeToAllAsync(FromAll.Start, (s, e, c) => { Console.WriteLine( $"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); diff --git a/samples/subscribing-to-streams/Program.cs b/samples/subscribing-to-streams/Program.cs index 6286305c1..724b1b2e5 100644 --- a/samples/subscribing-to-streams/Program.cs +++ b/samples/subscribing-to-streams/Program.cs @@ -1,5 +1,6 @@ using System; using System.Net.Http; +using System.Reflection.Metadata; using System.Threading; using System.Threading.Tasks; using EventStore.Client; @@ -20,6 +21,7 @@ static async Task Main(string[] args) { 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); @@ -29,30 +31,31 @@ await client.SubscribeToStreamAsync("some-stream", #region subscribe-to-stream-from-position await client.SubscribeToStreamAsync( "some-stream", - StreamPosition.FromInt64(20), + FromStream.After(StreamPosition.FromInt64(20)), EventAppeared); #endregion subscribe-to-stream-from-position #region subscribe-to-stream-live await client.SubscribeToStreamAsync( "some-stream", - StreamPosition.End, + FromStream.End, EventAppeared); #endregion subscribe-to-stream-live #region subscribe-to-stream-resolving-linktos await client.SubscribeToStreamAsync( "$et-myEventType", - StreamPosition.Start, + FromStream.Start, EventAppeared, resolveLinkTos: true); #endregion subscribe-to-stream-resolving-linktos #region subscribe-to-stream-subscription-dropped - var checkpoint = StreamPosition.Start; + + var checkpoint = await ReadStreamCheckpointAsync(); await client.SubscribeToStreamAsync( "some-stream", - checkpoint, + checkpoint is null ? FromStream.Start : FromStream.After(checkpoint.Value), eventAppeared: async (subscription, evnt, cancellationToken) => { await HandleEvent(evnt); checkpoint = evnt.OriginalEventNumber; @@ -70,7 +73,7 @@ await client.SubscribeToStreamAsync( private static async Task SubscribeToAll(EventStoreClient client) { #region subscribe-to-all await client.SubscribeToAllAsync( - SubscriptionPosition.Start, + FromAll.Start, async (subscription, evnt, cancellationToken) => { Console.WriteLine($"Received event {evnt.OriginalEventNumber}@{evnt.OriginalStreamId}"); await HandleEvent(evnt); @@ -84,23 +87,23 @@ await client.SubscribeToAllAsync( }); await client.SubscribeToAllAsync( - SubscriptionPosition.After(result.LogPosition), + FromAll.After(result.LogPosition), EventAppeared); #endregion subscribe-to-all-from-position #region subscribe-to-all-live await client.SubscribeToAllAsync( - SubscriptionPosition.Live, + FromAll.End, EventAppeared); #endregion subscribe-to-all-live #region subscribe-to-all-subscription-dropped - var checkpoint = SubscriptionPosition.Start; + var checkpoint = await ReadCheckpointAsync(); await client.SubscribeToAllAsync( - checkpoint, + checkpoint is null ? FromAll.Start : FromAll.After(checkpoint.Value), eventAppeared: async (subscription, evnt, cancellationToken) => { await HandleEvent(evnt); - checkpoint = SubscriptionPosition.After(evnt.OriginalPosition!.Value); + checkpoint = evnt.OriginalPosition!.Value; }, subscriptionDropped: ((subscription, reason, exception) => { Console.WriteLine($"Subscription was dropped due to {reason}. {exception}"); @@ -116,7 +119,7 @@ private static async Task SubscribeToFiltered(EventStoreClient client) { #region stream-prefix-filtered-subscription var prefixStreamFilter = new SubscriptionFilterOptions(StreamFilter.Prefix("test-", "other-")); await client.SubscribeToAllAsync( - SubscriptionPosition.Start, + FromAll.Start, EventAppeared, filterOptions: prefixStreamFilter); #endregion stream-prefix-filtered-subscription @@ -129,7 +132,7 @@ await client.SubscribeToAllAsync( private static async Task OverridingUserCredentials(EventStoreClient client) { #region overriding-user-credentials await client.SubscribeToAllAsync( - SubscriptionPosition.Start, + FromAll.Start, EventAppeared, userCredentials: new UserCredentials("admin", "changeit")); #endregion overriding-user-credentials @@ -147,7 +150,13 @@ private static Task HandleEvent(ResolvedEvent evnt) { return Task.CompletedTask; } - private static void Resubscribe(StreamPosition checkpoint) { } - private static void Resubscribe(SubscriptionPosition checkpoint) { } + private static void Resubscribe(StreamPosition? checkpoint) { } + private static void Resubscribe(Position? checkpoint) { } + + private static Task ReadStreamCheckpointAsync() => + Task.FromResult(new StreamPosition?()); + + private static Task ReadCheckpointAsync() => + Task.FromResult(new Position?()); } } diff --git a/src/EventStore.Client.Streams/EventStoreClient.Subscriptions.cs b/src/EventStore.Client.Streams/EventStoreClient.Subscriptions.cs index 9d4b6070f..27fd3eb69 100644 --- a/src/EventStore.Client.Streams/EventStoreClient.Subscriptions.cs +++ b/src/EventStore.Client.Streams/EventStoreClient.Subscriptions.cs @@ -6,33 +6,10 @@ #nullable enable namespace EventStore.Client { public partial class EventStoreClient { - private Task SubscribeToAllAsync( - Func eventAppeared, - EventStoreClientOperationOptions operationOptions, - bool resolveLinkTos = false, - Action? subscriptionDropped = default, - SubscriptionFilterOptions? filterOptions = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - operationOptions.TimeoutAfter = DeadLine.None; - - return StreamSubscription.Confirm(ReadInternal(new ReadReq { - Options = new ReadReq.Types.Options { - ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, - ResolveLinks = resolveLinkTos, - All = new ReadReq.Types.Options.Types.AllOptions { - Start = new Empty() - }, - Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), - Filter = GetFilterOptions(filterOptions)! - } - }, operationOptions, userCredentials, cancellationToken), eventAppeared, subscriptionDropped, _log, - filterOptions?.CheckpointReached, cancellationToken); - } - /// - /// Subscribes to all events. Use this when you have no checkpoint. + /// Subscribes to all events. /// + /// A (exclusive of) to start the subscription from. /// A Task invoked and awaited when a new event is received over the subscription. /// An to configure the operation's options. /// Whether to resolve LinkTo events automatically. @@ -42,6 +19,7 @@ private Task SubscribeToAllAsync( /// The optional . /// public Task SubscribeToAllAsync( + FromAll start, Func eventAppeared, bool resolveLinkTos = false, Action? subscriptionDropped = default, @@ -52,25 +30,13 @@ public Task SubscribeToAllAsync( var operationOptions = Settings.OperationOptions.Clone(); configureOperationOptions?.Invoke(operationOptions); - return SubscribeToAllAsync(eventAppeared, operationOptions, resolveLinkTos, subscriptionDropped, - filterOptions, userCredentials, cancellationToken); - } - - private Task SubscribeToAllAsync(Position start, - Func eventAppeared, - EventStoreClientOperationOptions operationOptions, - bool resolveLinkTos = false, - Action? subscriptionDropped = default, - SubscriptionFilterOptions? filterOptions = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { operationOptions.TimeoutAfter = DeadLine.None; return StreamSubscription.Confirm(ReadInternal(new ReadReq { Options = new ReadReq.Types.Options { ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, ResolveLinks = resolveLinkTos, - All = ReadReq.Types.Options.Types.AllOptions.FromPosition(start), + All = ReadReq.Types.Options.Types.AllOptions.FromSubscriptionPosition(start), Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), Filter = GetFilterOptions(filterOptions)! } @@ -79,136 +45,39 @@ private Task SubscribeToAllAsync(Position start, } /// - /// Subscribes to all events from a checkpoint. This is exclusive of. + /// Subscribes to a stream from a checkpoint. /// - /// A (exclusive of) to start the subscription from. + /// A (exclusive of) to start the subscription from. + /// The name of the stream to read events from. /// A Task invoked and awaited when a new event is received over the subscription. /// An to configure the operation's options. /// Whether to resolve LinkTo events automatically. /// An action invoked if the subscription is dropped. - /// The optional to apply. /// The optional user credentials to perform operation with. /// The optional . /// - public Task SubscribeToAllAsync(Position start, + public Task SubscribeToStreamAsync(string streamName, + FromStream start, Func eventAppeared, bool resolveLinkTos = false, Action? subscriptionDropped = default, - SubscriptionFilterOptions? filterOptions = null, Action? configureOperationOptions = null, UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { var operationOptions = Settings.OperationOptions.Clone(); configureOperationOptions?.Invoke(operationOptions); - return SubscribeToAllAsync(start, eventAppeared, operationOptions, resolveLinkTos, subscriptionDropped, - filterOptions, userCredentials, cancellationToken); - } - - private Task SubscribeToStreamAsync(string streamName, - Func eventAppeared, - EventStoreClientOperationOptions operationOptions, - bool resolveLinkTos = false, - Action? subscriptionDropped = default, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { operationOptions.TimeoutAfter = DeadLine.None; return StreamSubscription.Confirm(ReadInternal(new ReadReq { Options = new ReadReq.Types.Options { ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, ResolveLinks = resolveLinkTos, - Stream = new ReadReq.Types.Options.Types.StreamOptions { - Start = new Empty(), - StreamIdentifier = streamName - }, + Stream = ReadReq.Types.Options.Types.StreamOptions.FromSubscriptionPosition(streamName, start), Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions() } }, operationOptions, userCredentials, cancellationToken), eventAppeared, subscriptionDropped, _log, cancellationToken: cancellationToken); } - - /// - /// Subscribes to all events in a stream. Use this when you have no checkpoint. - /// - /// The name of the stream to read events from. - /// A Task invoked and awaited when a new event is received over the subscription. - /// An to configure the operation's options. - /// Whether to resolve LinkTo events automatically. - /// An action invoked if the subscription is dropped. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public Task SubscribeToStreamAsync(string streamName, - Func eventAppeared, - bool resolveLinkTos = false, - Action? subscriptionDropped = default, - Action? configureOperationOptions = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var operationOptions = Settings.OperationOptions.Clone(); - configureOperationOptions?.Invoke(operationOptions); - - return SubscribeToStreamAsync(streamName, eventAppeared, operationOptions, resolveLinkTos, - subscriptionDropped, - userCredentials, cancellationToken); - } - - private Task SubscribeToStreamAsync(string streamName, - StreamPosition start, - Func eventAppeared, - EventStoreClientOperationOptions operationOptions, - bool resolveLinkTos = false, - Action? subscriptionDropped = default, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - operationOptions.TimeoutAfter = DeadLine.None; - - return StreamSubscription.Confirm(ReadInternal(new ReadReq { - Options = new ReadReq.Types.Options { - ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, - ResolveLinks = resolveLinkTos, - Stream = start == StreamPosition.End - ? new ReadReq.Types.Options.Types.StreamOptions { - End = new Empty(), - StreamIdentifier = streamName - } - : new ReadReq.Types.Options.Types.StreamOptions { - Revision = start, - StreamIdentifier = streamName - }, - Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions() - } - }, - operationOptions, userCredentials, cancellationToken), eventAppeared, subscriptionDropped, _log, - cancellationToken: cancellationToken); - } - - /// - /// Subscribes to a stream from a checkpoint. This is exclusive of. - /// - /// A (exclusive of) to start the subscription from. - /// The name of the stream to read events from. - /// A Task invoked and awaited when a new event is received over the subscription. - /// An to configure the operation's options. - /// Whether to resolve LinkTo events automatically. - /// An action invoked if the subscription is dropped. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public Task SubscribeToStreamAsync(string streamName, - StreamPosition start, - Func eventAppeared, - bool resolveLinkTos = false, - Action? subscriptionDropped = default, - Action? configureOperationOptions = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var operationOptions = Settings.OperationOptions.Clone(); - configureOperationOptions?.Invoke(operationOptions); - - return SubscribeToStreamAsync(streamName, start, eventAppeared, operationOptions, resolveLinkTos, - subscriptionDropped, userCredentials, cancellationToken); - } } } diff --git a/src/EventStore.Client.Streams/Streams/ReadReq.cs b/src/EventStore.Client.Streams/Streams/ReadReq.cs index c60020c8c..ed5ca3af7 100644 --- a/src/EventStore.Client.Streams/Streams/ReadReq.cs +++ b/src/EventStore.Client.Streams/Streams/ReadReq.cs @@ -6,6 +6,27 @@ partial class Types { partial class Options { partial class Types { partial class StreamOptions { + public static StreamOptions FromSubscriptionPosition(string streamName, + FromStream fromStream) { + if (fromStream == FromStream.End) { + return new StreamOptions { + StreamIdentifier = streamName, + End = new Empty() + }; + } + + if (fromStream == FromStream.Start) { + return new StreamOptions { + StreamIdentifier = streamName, + Start = new Empty() + }; + } + + return new StreamOptions { + StreamIdentifier = streamName, + Revision = fromStream.ToUInt64() + }; + } public static StreamOptions FromStreamNameAndRevision( string streamName, StreamPosition streamRevision) { @@ -35,23 +56,25 @@ public static StreamOptions FromStreamNameAndRevision( } partial class AllOptions { - public static AllOptions FromPosition(Client.Position position) { - if (position == Client.Position.End) { + public static AllOptions FromSubscriptionPosition(FromAll position) { + if (position == FromAll.End) { return new AllOptions { End = new Empty() }; } - if (position == Client.Position.Start) { + if (position == FromAll.Start) { return new AllOptions { Start = new Empty() }; } + var (c, p) = position.ToUInt64(); + return new AllOptions { Position = new Position { - CommitPosition = position.CommitPosition, - PreparePosition = position.PreparePosition + CommitPosition = c, + PreparePosition = p } }; } diff --git a/src/EventStore.Client/FromAll.cs b/src/EventStore.Client/FromAll.cs new file mode 100644 index 000000000..697584331 --- /dev/null +++ b/src/EventStore.Client/FromAll.cs @@ -0,0 +1,101 @@ +using System; + +#nullable enable +namespace EventStore.Client { + /// + /// A structure representing the logical position of a subscription to all. /> + /// + public readonly struct FromAll : IEquatable, IComparable, IComparable { + /// + /// Represents a when no events have been seen (i.e., the beginning). + /// + public static readonly FromAll Start = new(null); + + /// + /// Represents a to receive events written after the subscription is confirmed. + /// + public static readonly FromAll End = new(Position.End); + + /// + /// Returns a for the given . + /// + /// The . + /// + /// + public static FromAll After(Position position) => position == Position.End + ? throw new ArgumentException($"Use '{nameof(FromAll)}.{nameof(End)}.'", nameof(position)) + : new(position); + + private readonly Position? _value; + + private FromAll(Position? value) => _value = value; + + /// + /// Converts the to a . + /// It is not meant to be used directly from your code. + /// + /// + /// + public (ulong commitPosition, ulong preparePosition) ToUInt64() => this == Start + ? throw new InvalidOperationException( + $"{nameof(FromAll)}.{nameof(Start)} may not be converted.") + : (_value!.Value.CommitPosition, _value!.Value.PreparePosition); + + /// + public bool Equals(FromAll other) => Nullable.Equals(_value, other._value); + + /// + public override bool Equals(object? obj) => obj is FromAll other && Equals(other); + + /// + public override int GetHashCode() => _value.GetHashCode(); + +#pragma warning disable CS1591 + public static bool operator ==(FromAll left, FromAll right) => + Nullable.Equals(left, right); + + public static bool operator !=(FromAll left, FromAll right) => + !Nullable.Equals(left, right); + + public static bool operator >(FromAll left, FromAll right) => + left.CompareTo(right) > 0; + + public static bool operator <(FromAll left, FromAll right) => + left.CompareTo(right) < 0; + + public static bool operator >=(FromAll left, FromAll right) => + left.CompareTo(right) >= 0; + + public static bool operator <=(FromAll left, FromAll right) => + left.CompareTo(right) <= 0; +#pragma warning restore CS1591 + + /// + public int CompareTo(FromAll other) => (_value, other._value) switch { + (null, null) => 0, + (null, _) => -1, + (_, null) => 1, + _ => _value.Value.CompareTo(other._value.Value) + }; + + /// + public int CompareTo(object? obj) => obj switch { + null => 1, + FromAll other => CompareTo(other), + _ => throw new ArgumentException($"Object is not a {nameof(FromAll)}"), + }; + + /// + public override string ToString() { + if (_value is null) { + return "Start"; + } + + if (_value == Position.End) { + return "Live"; + } + + return _value.Value.ToString(); + } + } +} diff --git a/src/EventStore.Client/FromStream.cs b/src/EventStore.Client/FromStream.cs new file mode 100644 index 000000000..ae8dc5660 --- /dev/null +++ b/src/EventStore.Client/FromStream.cs @@ -0,0 +1,101 @@ +using System; + +#nullable enable +namespace EventStore.Client { + /// + /// A structure representing the logical position of a subscription. /> + /// + public readonly struct FromStream : IEquatable, IComparable, IComparable { + /// + /// Represents a when no events have been seen (i.e., the beginning). + /// + public static readonly FromStream Start = new(null); + + /// + /// Represents a to receive events written after the subscription is confirmed. + /// + public static readonly FromStream End = new(StreamPosition.End); + + private readonly StreamPosition? _value; + + /// + /// Returns a for the given . + /// + /// The . + /// + /// + public static FromStream After(StreamPosition streamPosition) => + streamPosition == StreamPosition.End + ? throw new ArgumentException($"Use '{nameof(FromStream)}.{nameof(End)}.'", nameof(streamPosition)) + : new(streamPosition); + + private FromStream(StreamPosition? value) => _value = value; + + /// + /// Converts the to a . It is not meant to be used directly from your code. + /// + /// + /// + public ulong ToUInt64() => this == Start + ? throw new InvalidOperationException( + $"{nameof(FromStream)}.{nameof(Start)} may not be converted.") + : _value!.Value.ToUInt64(); + + /// + public bool Equals(FromStream other) => Nullable.Equals(_value, other._value); + + /// + public override bool Equals(object? obj) => obj is FromStream other && Equals(other); + + /// + public override int GetHashCode() => _value.GetHashCode(); + +#pragma warning disable CS1591 + public static bool operator ==(FromStream left, FromStream right) => + left.Equals(right); + + public static bool operator !=(FromStream left, FromStream right) => + !left.Equals(right); + + public static bool operator <(FromStream left, FromStream right) => + left.CompareTo(right) < 0; + + public static bool operator >(FromStream left, FromStream right) => + left.CompareTo(right) > 0; + + public static bool operator <=(FromStream left, FromStream right) => + left.CompareTo(right) <= 0; + + public static bool operator >=(FromStream left, FromStream right) => + left.CompareTo(right) >= 0; +#pragma warning restore CS1591 + + /// + public int CompareTo(FromStream other) => (_value, other._value) switch { + (null, null) => 0, + (null, _) => -1, + (_, null) => 1, + _ => _value.Value.CompareTo(other._value.Value) + }; + + /// + public int CompareTo(object? obj) => obj switch { + null => 1, + FromStream other => CompareTo(other), + _ => throw new ArgumentException($"Object is not a {nameof(FromStream)}"), + }; + + /// + public override string ToString() { + if (_value is null) { + return "Start"; + } + + if (_value == StreamPosition.End) { + return "Live"; + } + + return _value.Value.ToString(); + } + } +} diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/update_existing_with_check_point.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/update_existing_with_check_point.cs index 0663c1b37..49f1a1fbe 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/update_existing_with_check_point.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/update_existing_with_check_point.cs @@ -7,7 +7,6 @@ namespace EventStore.Client.SubscriptionToAll { public class update_existing_with_check_point : IClassFixture { - private const string Group = "existing-with-check-point"; private readonly Fixture _fixture; @@ -24,7 +23,7 @@ public async Task resumes_from_check_point() { public class Fixture : EventStoreClientFixture { public Task Resumed => _resumedSource.Task; public Position CheckPoint { get; private set; } - + private readonly TaskCompletionSource<(SubscriptionDroppedReason, Exception)> _droppedSource; private readonly TaskCompletionSource _resumedSource; private readonly TaskCompletionSource _checkPointSource; @@ -43,12 +42,12 @@ public Fixture() { _appearedEvents = new List(); _events = CreateTestEvents(5).ToArray(); } - + protected override async Task Given() { foreach (var e in _events) { await StreamsClient.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.Any, new[] {e}); } - + await Client.CreateToAllAsync(Group, new PersistentSubscriptionSettings( minCheckPointCount: 5, @@ -58,8 +57,9 @@ await Client.CreateToAllAsync(Group, var checkPointStream = $"$persistentsubscription-$all::{Group}-checkpoint"; _checkPointSubscription = await StreamsClient.SubscribeToStreamAsync(checkPointStream, - (s, e, ct) => { - _checkPointSource.TrySetResult(e); + FromStream.Start, + (_, e, _) => { + _checkPointSource.TrySetResult(e); return Task.CompletedTask; }, subscriptionDropped: (_, reason, ex) => { if (ex is not null) { @@ -69,7 +69,7 @@ await Client.CreateToAllAsync(Group, } }, userCredentials: TestCredentials.Root); - + _firstSubscription = await Client.SubscribeToAllAsync(Group, eventAppeared: async (s, e, r, ct) => { _appearedEvents.Add(e); @@ -91,14 +91,13 @@ protected override async Task When() { await Client.UpdateToAllAsync(Group, new PersistentSubscriptionSettings(), TestCredentials.Root); await _droppedSource.Task.WithTimeout(); - + _secondSubscription = await Client.SubscribeToAllAsync(Group, async (s, e, r, ct) => { _resumedSource.TrySetResult(e); await s.Ack(e); }, (_, reason, ex) => { - if (ex is not null) { _resumedSource.TrySetException(ex); } else { @@ -106,7 +105,7 @@ protected override async Task When() { } }, TestCredentials.Root); - + foreach (var e in _events) { await StreamsClient.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.Any, new[] {e}); } diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/update_existing_with_check_point_filtered.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/update_existing_with_check_point_filtered.cs index fb752100d..f16616568 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/update_existing_with_check_point_filtered.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/update_existing_with_check_point_filtered.cs @@ -7,7 +7,6 @@ namespace EventStore.Client.SubscriptionToAll { public class update_existing_with_check_point_filtered : IClassFixture { - private const string Group = "existing-with-check-point-filtered"; private readonly Fixture _fixture; @@ -24,7 +23,7 @@ public async Task resumes_from_check_point() { public class Fixture : EventStoreClientFixture { public Task Resumed => _resumedSource.Task; public Position CheckPoint { get; private set; } - + private readonly TaskCompletionSource<(SubscriptionDroppedReason, Exception)> _droppedSource; private readonly TaskCompletionSource _resumedSource; private readonly TaskCompletionSource _checkPointSource; @@ -43,7 +42,7 @@ public Fixture() { _appearedEvents = new List(); _events = CreateTestEvents(5).ToArray(); } - + protected override async Task Given() { foreach (var e in _events) { await StreamsClient.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.NoStream, new[] {e}); @@ -59,6 +58,7 @@ await Client.CreateToAllAsync(Group, var checkPointStream = $"$persistentsubscription-$all::{Group}-checkpoint"; _checkPointSubscription = await StreamsClient.SubscribeToStreamAsync(checkPointStream, + FromStream.Start, (_, e, ct) => { _checkPointSource.TrySetResult(e); return Task.CompletedTask; @@ -71,7 +71,7 @@ await Client.CreateToAllAsync(Group, } }, userCredentials: TestCredentials.Root); - + _firstSubscription = await Client.SubscribeToAllAsync(Group, eventAppeared: async (s, e, r, ct) => { _appearedEvents.Add(e); @@ -93,7 +93,7 @@ protected override async Task When() { await Client.UpdateToAllAsync(Group, new PersistentSubscriptionSettings(), TestCredentials.Root); await _droppedSource.Task.WithTimeout(); - + _secondSubscription = await Client.SubscribeToAllAsync(Group, eventAppeared: async (s, e, r, ct) => { _resumedSource.TrySetResult(e); @@ -108,7 +108,7 @@ protected override async Task When() { } }, userCredentials: TestCredentials.Root); - + foreach (var e in _events) { await StreamsClient.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.NoStream, new[] {e}); } diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/when_writing_and_filtering_out_events.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/when_writing_and_filtering_out_events.cs index 29fe9dafa..632f13be1 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/when_writing_and_filtering_out_events.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToAll/when_writing_and_filtering_out_events.cs @@ -7,7 +7,6 @@ namespace EventStore.Client.SubscriptionToAll { public class when_writing_and_filtering_out_events : IClassFixture { - private const string Group = "filtering-out-events"; private readonly Fixture _fixture; @@ -29,7 +28,7 @@ public class Fixture : EventStoreClientFixture { public Position FirstCheckPoint { get; private set; } public EventData[] Events => _events.ToArray(); public ResolvedEvent[] AppearedEvents => _appearedEvents.ToArray(); - + private readonly TaskCompletionSource _firstCheckPointSource, _secondCheckPointSource; private PersistentSubscription _subscription; private StreamSubscription _checkPointSubscription; @@ -46,12 +45,12 @@ public Fixture() { _checkPoints = new List(); _events = CreateTestEvents(5).ToArray(); } - + protected override async Task Given() { foreach (var e in _events) { await StreamsClient.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.Any, new[] {e}); } - + await Client.CreateToAllAsync(Group, StreamFilter.Prefix("test"), new PersistentSubscriptionSettings( @@ -59,19 +58,21 @@ await Client.CreateToAllAsync(Group, checkPointAfter: TimeSpan.FromSeconds(1), startFrom: Position.Start), TestCredentials.Root); - + _checkPointSubscription = await StreamsClient.SubscribeToStreamAsync(_checkPointStream, - (s, e, ct) => { + FromStream.Start, + (_, e, _) => { if (_checkPoints.Count == 0) { - _firstCheckPointSource.TrySetResult(e); + _firstCheckPointSource.TrySetResult(e); } else { _secondCheckPointSource.TrySetResult(e); } + _checkPoints.Add(e); return Task.CompletedTask; }, userCredentials: TestCredentials.Root); - + _subscription = await Client.SubscribeToAllAsync(Group, eventAppeared: async (s, e, r, ct) => { _appearedEvents.Add(e); @@ -83,13 +84,13 @@ await Client.CreateToAllAsync(Group, userCredentials: TestCredentials.Root); await Task.WhenAll(_appeared.Task, _firstCheckPointSource.Task).WithTimeout(); - + FirstCheckPoint = _firstCheckPointSource.Task.Result.Event.Data.ParsePosition(); } protected override async Task When() { foreach (var e in _events) { - await StreamsClient.AppendToStreamAsync("filtered-out-stream-" + Guid.NewGuid(), + await StreamsClient.AppendToStreamAsync("filtered-out-stream-" + Guid.NewGuid(), StreamState.Any, new[] {e}); } } diff --git a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/update_existing_with_check_point.cs b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/update_existing_with_check_point.cs index bbf89654e..b5bd5f8af 100644 --- a/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/update_existing_with_check_point.cs +++ b/test/EventStore.Client.PersistentSubscriptions.Tests/SubscriptionToStream/update_existing_with_check_point.cs @@ -23,11 +23,11 @@ public async Task resumes_from_check_point() { var resumedEvent = await _fixture.Resumed.WithTimeout(TimeSpan.FromSeconds(10)); Assert.Equal(_fixture.CheckPoint.Next(), resumedEvent.Event.EventNumber); } - + public class Fixture : EventStoreClientFixture { public Task Resumed => _resumedSource.Task; public StreamPosition CheckPoint { get; private set; } - + private readonly TaskCompletionSource<(SubscriptionDroppedReason, Exception)> _droppedSource; private readonly TaskCompletionSource _resumedSource; private readonly TaskCompletionSource _checkPointSource; @@ -48,7 +48,7 @@ public Fixture() { protected override async Task Given() { await StreamsClient.AppendToStreamAsync(Stream, StreamState.NoStream, _events); - + await Client.CreateAsync(Stream, Group, new PersistentSubscriptionSettings( minCheckPointCount: 5, @@ -58,7 +58,8 @@ await Client.CreateAsync(Stream, Group, var checkPointStream = $"$persistentsubscription-{Stream}::{Group}-checkpoint"; await StreamsClient.SubscribeToStreamAsync(checkPointStream, - (_, e, ct) => { + FromStream.Start, + (_, e, _) => { _checkPointSource.TrySetResult(e); return Task.CompletedTask; }, @@ -70,7 +71,7 @@ await StreamsClient.SubscribeToStreamAsync(checkPointStream, } }, userCredentials: TestCredentials.Root); - + _firstSubscription = await Client.SubscribeToStreamAsync(Stream, Group, eventAppeared: async (s, e, r, ct) => { _appearedEvents.Add(e); @@ -86,13 +87,13 @@ await StreamsClient.SubscribeToStreamAsync(checkPointStream, CheckPoint = _checkPointSource.Task.Result.Event.Data.ParseStreamPosition(); } - + protected override async Task When() { // Force restart of the subscription await Client.UpdateAsync(Stream, Group, new PersistentSubscriptionSettings(), TestCredentials.Root); - + await _droppedSource.Task.WithTimeout(); - + _secondSubscription = await Client.SubscribeToStreamAsync(Stream, Group, eventAppeared: async (s, e, r, ct) => { _resumedSource.TrySetResult(e); @@ -106,7 +107,7 @@ protected override async Task When() { } }, userCredentials: TestCredentials.Root); - + await StreamsClient.AppendToStreamAsync(Stream, StreamState.Any, CreateTestEvents(1)); } diff --git a/test/EventStore.Client.Streams.Tests/Bugs/Issue_104.cs b/test/EventStore.Client.Streams.Tests/Bugs/Issue_104.cs index 66c31a6f5..1fc8636a1 100644 --- a/test/EventStore.Client.Streams.Tests/Bugs/Issue_104.cs +++ b/test/EventStore.Client.Streams.Tests/Bugs/Issue_104.cs @@ -19,19 +19,20 @@ public async Task subscription_does_not_send_checkpoint_reached_after_disposal() await _fixture.Client.AppendToStreamAsync(streamName, StreamRevision.None, _fixture.CreateTestEvents()); - var subscription = await _fixture.Client.SubscribeToAllAsync((_, __, ___) => { + var subscription = await _fixture.Client.SubscribeToAllAsync( + FromAll.Start, + (_, _, _) => { eventAppeared.TrySetResult(true); return Task.CompletedTask; - }, false, - (_, __, ____) => subscriptionDisposed.TrySetResult(true), - new SubscriptionFilterOptions(StreamFilter.Prefix(streamName), 1, (_, __, ____) => { - if (!subscriptionDisposed.Task.IsCompleted) { - return Task.CompletedTask; - } + }, false, (_, _, _) => subscriptionDisposed.TrySetResult(true), new SubscriptionFilterOptions( + StreamFilter.Prefix(streamName), 1, (_, _, _) => { + if (!subscriptionDisposed.Task.IsCompleted) { + return Task.CompletedTask; + } - checkpointReachAfterDisposed.TrySetResult(true); - return Task.CompletedTask; - }), userCredentials: new UserCredentials("admin", "changeit")); + checkpointReachAfterDisposed.TrySetResult(true); + return Task.CompletedTask; + }), userCredentials: new UserCredentials("admin", "changeit")); await eventAppeared.Task; diff --git a/test/EventStore.Client.Streams.Tests/Bugs/Issue_2544.cs b/test/EventStore.Client.Streams.Tests/Bugs/Issue_2544.cs index 968d81344..96a69a0a4 100644 --- a/test/EventStore.Client.Streams.Tests/Bugs/Issue_2544.cs +++ b/test/EventStore.Client.Streams.Tests/Bugs/Issue_2544.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -33,7 +32,8 @@ public async Task subscribe_to_stream(int iteration) { var streamName = $"{_fixture.GetStreamName()}_{iteration}"; using var _ = await _fixture.Client.SubscribeToStreamAsync(streamName, - (_, e, ct) => EventAppeared(e, streamName), subscriptionDropped: SubscriptionDropped); + FromStream.Start, + (_, e, _) => EventAppeared(e, streamName), subscriptionDropped: SubscriptionDropped); await AppendEvents(streamName); @@ -44,8 +44,8 @@ public async Task subscribe_to_stream(int iteration) { public async Task subscribe_to_all(int iteration) { var streamName = $"{_fixture.GetStreamName()}_{iteration}"; - using var _ = await _fixture.Client.SubscribeToAllAsync((_, e, ct) => EventAppeared(e, streamName), - subscriptionDropped: SubscriptionDropped); + using var _ = await _fixture.Client.SubscribeToAllAsync(FromAll.Start, + (_, e, _) => EventAppeared(e, streamName), false, SubscriptionDropped); await AppendEvents(streamName); @@ -56,9 +56,9 @@ public async Task subscribe_to_all(int iteration) { public async Task subscribe_to_all_filtered(int iteration) { var streamName = $"{_fixture.GetStreamName()}_{iteration}"; - using var _ = await _fixture.Client.SubscribeToAllAsync((_, e, ct) => EventAppeared(e, streamName), - subscriptionDropped: SubscriptionDropped, - filterOptions: new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents())); + using var _ = await _fixture.Client.SubscribeToAllAsync(FromAll.Start, + (_, e, _) => EventAppeared(e, streamName), false, SubscriptionDropped, + new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents())); await AppendEvents(streamName); diff --git a/test/EventStore.Client.Streams.Tests/Security/SecurityFixture.cs b/test/EventStore.Client.Streams.Tests/Security/SecurityFixture.cs index a0e4e08bc..e2423b747 100644 --- a/test/EventStore.Client.Streams.Tests/Security/SecurityFixture.cs +++ b/test/EventStore.Client.Streams.Tests/Security/SecurityFixture.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -136,7 +135,7 @@ public Task ReadStreamBackward(string streamId, UserCredentials userCredentials public Task AppendStream(string streamId, UserCredentials userCredentials = default) => Client.AppendToStreamAsync(streamId, StreamState.Any, CreateTestEvents(3), - userCredentials: userCredentials) + userCredentials: userCredentials) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public Task ReadAllForward(UserCredentials userCredentials = default) => @@ -160,38 +159,39 @@ public Task ReadMeta(string streamId, UserCredentials user public Task WriteMeta(string streamId, UserCredentials userCredentials = default, string role = default) => Client.SetStreamMetadataAsync(streamId, StreamState.Any, - new StreamMetadata(acl: new StreamAcl( - writeRole: role, - readRole: role, - metaWriteRole: role, - metaReadRole: role)), - userCredentials: userCredentials) + new StreamMetadata(acl: new StreamAcl( + writeRole: role, + readRole: role, + metaWriteRole: role, + metaReadRole: role)), + userCredentials: userCredentials) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); public async Task SubscribeToStream(string streamId, UserCredentials userCredentials = default) { var source = new TaskCompletionSource(); - using (await Client.SubscribeToStreamAsync(streamId, (_, _, _) => { - source.TrySetResult(true); - return Task.CompletedTask; - }, - subscriptionDropped: (_, _, ex) => { - if (ex == null) source.TrySetResult(true); - else source.TrySetException(ex); - }, userCredentials: userCredentials).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs))) { + using (await Client.SubscribeToStreamAsync(streamId, FromStream.Start, (_, _, _) => { + source.TrySetResult(true); + return Task.CompletedTask; + }, + subscriptionDropped: (_, _, ex) => { + if (ex == null) source.TrySetResult(true); + else source.TrySetException(ex); + }, userCredentials: userCredentials).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs))) { await source.Task.WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); } } public async Task SubscribeToAll(UserCredentials userCredentials = default) { var source = new TaskCompletionSource(); - using (await Client.SubscribeToAllAsync((_, _, _) => { - source.TrySetResult(true); - return Task.CompletedTask; - }, - subscriptionDropped: (_, _, ex) => { - if (ex == null) source.TrySetResult(true); - else source.TrySetException(ex); - }, userCredentials: userCredentials).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs))) { + using (await Client.SubscribeToAllAsync(FromAll.Start, + (_, _, _) => { + source.TrySetResult(true); + return Task.CompletedTask; + }, false, (_, _, ex) => { + if (ex == null) source.TrySetResult(true); + else source.TrySetException(ex); + }, null, null, + userCredentials, default).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs))) { await source.Task.WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); } } @@ -199,7 +199,7 @@ public async Task SubscribeToAll(UserCredentials userCredentials = default) { public async Task CreateStreamWithMeta(StreamMetadata metadata, [CallerMemberName] string streamId = "") { await Client.SetStreamMetadataAsync(streamId, StreamState.NoStream, - metadata, userCredentials: TestCredentials.TestAdmin) + metadata, userCredentials: TestCredentials.TestAdmin) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); return streamId; } diff --git a/test/EventStore.Client.Streams.Tests/subscribe_resolve_link_to.cs b/test/EventStore.Client.Streams.Tests/subscribe_resolve_link_to.cs index 3eefb0083..cf7860a13 100644 --- a/test/EventStore.Client.Streams.Tests/subscribe_resolve_link_to.cs +++ b/test/EventStore.Client.Streams.Tests/subscribe_resolve_link_to.cs @@ -35,8 +35,9 @@ await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, beforeEv .WithTimeout(); using var subscription = await _fixture.Client - .SubscribeToStreamAsync($"$et-{EventStoreClientFixtureBase.TestEventType}", EventAppeared, true, - SubscriptionDropped, userCredentials: TestCredentials.Root) + .SubscribeToStreamAsync($"$et-{EventStoreClientFixtureBase.TestEventType}", + FromStream.Start, EventAppeared, true, SubscriptionDropped, + userCredentials: TestCredentials.Root) .WithTimeout(); await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, afterEvents) @@ -88,12 +89,11 @@ public async Task all_subscription() { await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, beforeEvents) .WithTimeout(); - using var subscription = await _fixture.Client - .SubscribeToAllAsync(EventAppeared, true, SubscriptionDropped, userCredentials: TestCredentials.Root) + 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 _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, afterEvents).WithTimeout(); await appeared.Task.WithTimeout(); @@ -108,6 +108,7 @@ 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()) { @@ -144,8 +145,10 @@ public async Task all_filtered_subscription() { var result = await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, beforeEvents) .WithTimeout(); - using var subscription = await _fixture.Client.SubscribeToAllAsync(EventAppeared, true, SubscriptionDropped, - new SubscriptionFilterOptions(StreamFilter.Prefix($"$et-{EventStoreClientFixtureBase.TestEventType}")), + using var subscription = await _fixture.Client.SubscribeToAllAsync(FromAll.Start, + EventAppeared, true, SubscriptionDropped, + new SubscriptionFilterOptions( + StreamFilter.Prefix($"$et-{EventStoreClientFixtureBase.TestEventType}")), userCredentials: TestCredentials.Root) .WithTimeout(); @@ -165,6 +168,7 @@ 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()) { @@ -187,8 +191,8 @@ public class Fixture : EventStoreClientFixture { ["EVENTSTORE_RUN_PROJECTIONS"] = "All", ["EVENTSTORE_START_STANDARD_PROJECTIONS"] = "True" }) { - } + protected override Task Given() => Task.CompletedTask; protected override Task When() => Task.CompletedTask; } diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_all.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_all.cs index acc98a746..22d188cf4 100644 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_all.cs +++ b/test/EventStore.Client.Streams.Tests/subscribe_to_all.cs @@ -23,8 +23,8 @@ public subscribe_to_all(ITestOutputHelper outputHelper) { public async Task calls_subscription_dropped_when_disposed() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); - using var subscription = await _fixture.Client - .SubscribeToAllAsync(EventAppeared, false, SubscriptionDropped) + using var subscription = await _fixture.Client.SubscribeToAllAsync(FromAll.Start, + EventAppeared, false, SubscriptionDropped) .WithTimeout(); if (dropped.Task.IsCompleted) { @@ -50,8 +50,8 @@ public async Task calls_subscription_dropped_when_error_processing_event() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); var expectedException = new Exception("Error"); - using var subscription = await _fixture.Client - .SubscribeToAllAsync(EventAppeared, false, SubscriptionDropped) + using var subscription = await _fixture.Client.SubscribeToAllAsync(FromAll.Start, + EventAppeared, false, SubscriptionDropped) .WithTimeout(); await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); @@ -73,8 +73,8 @@ public async Task subscribe_to_empty_database() { var appeared = new TaskCompletionSource(); var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); - using var subscription = await _fixture.Client - .SubscribeToAllAsync(EventAppeared, false, SubscriptionDropped) + using var subscription = await _fixture.Client.SubscribeToAllAsync(FromAll.Start, + EventAppeared, false, SubscriptionDropped) .WithTimeout(); Assert.False(appeared.Task.IsCompleted); @@ -115,8 +115,8 @@ await _fixture.Client.AppendToStreamAsync($"stream-{@event.EventId:n}", StreamSt new[] {@event}); } - using var subscription = await _fixture.Client - .SubscribeToAllAsync(EventAppeared, false, SubscriptionDropped) + using var subscription = await _fixture.Client.SubscribeToAllAsync(FromAll.Start, + EventAppeared, false, SubscriptionDropped) .WithTimeout(); foreach (var @event in afterEvents) { diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered.cs index 6b977ea7b..8838e2372 100644 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered.cs +++ b/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered.cs @@ -40,9 +40,9 @@ await _fixture.Client.AppendToStreamAsync($"{streamPrefix}_{Guid.NewGuid():n}", StreamState.NoStream, new[] {e}); } - using var subscription = await _fixture.Client.SubscribeToAllAsync(EventAppeared, false, - filterOptions: new SubscriptionFilterOptions(filter, 5, CheckpointReached), - subscriptionDropped: SubscriptionDropped) + using var subscription = await _fixture.Client.SubscribeToAllAsync(FromAll.Start, + EventAppeared, false, SubscriptionDropped, + new SubscriptionFilterOptions(filter, 5, CheckpointReached)) .WithTimeout(); await Task.WhenAll(appeared.Task, checkpointSeen.Task).WithTimeout(); @@ -111,9 +111,9 @@ await _fixture.Client.AppendToStreamAsync($"{streamPrefix}_{Guid.NewGuid():n}", StreamState.NoStream, new[] {e}); } - using var subscription = await _fixture.Client.SubscribeToAllAsync(EventAppeared, false, - filterOptions: new SubscriptionFilterOptions(filter, 5, CheckpointReached), - subscriptionDropped: SubscriptionDropped) + using var subscription = await _fixture.Client.SubscribeToAllAsync(FromAll.Start, + EventAppeared, false, SubscriptionDropped, + new SubscriptionFilterOptions(filter, 5, CheckpointReached)) .WithTimeout(); foreach (var e in afterEvents) { 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 index 7a3722887..0ef4de839 100644 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered_live.cs +++ b/test/EventStore.Client.Streams.Tests/subscribe_to_all_filtered_live.cs @@ -3,8 +3,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using EventStore.Client.Streams; -using Serilog; using Xunit; using Xunit.Abstractions; @@ -40,9 +38,8 @@ await _fixture.Client.AppendToStreamAsync($"{streamPrefix}_{Guid.NewGuid():n}", StreamState.NoStream, new[] {e}); } - using var subscription = await _fixture.Client.SubscribeToAllAsync(Position.End, EventAppeared, false, - filterOptions: new SubscriptionFilterOptions(filter), - subscriptionDropped: SubscriptionDropped) + using var subscription = await _fixture.Client.SubscribeToAllAsync(FromAll.End, EventAppeared, + false, SubscriptionDropped, new SubscriptionFilterOptions(filter)) .WithTimeout(); foreach (var e in afterEvents) { 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 index 7187744b6..ce96a5600 100644 --- 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 @@ -3,7 +3,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using EventStore.Client.Streams; using Xunit; using Xunit.Abstractions; @@ -46,9 +45,9 @@ await _fixture.Client.AppendToStreamAsync($"{streamPrefix}_{Guid.NewGuid():n}", await _fixture.Client.AppendToStreamAsync(Guid.NewGuid().ToString(), StreamState.NoStream, _fixture.CreateTestEvents(256)); - using var subscription = await _fixture.Client.SubscribeToAllAsync(writeResult.LogPosition, EventAppeared, - false, filterOptions: new SubscriptionFilterOptions(filter, 4, CheckpointReached), - subscriptionDropped: SubscriptionDropped) + using var subscription = await _fixture.Client.SubscribeToAllAsync( + FromAll.After(writeResult.LogPosition), + EventAppeared, false, SubscriptionDropped, new SubscriptionFilterOptions(filter, 4, CheckpointReached)) .WithTimeout(); foreach (var e in afterEvents) { diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_all_live.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_all_live.cs index 51d5305e3..46b273451 100644 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_all_live.cs +++ b/test/EventStore.Client.Streams.Tests/subscribe_to_all_live.cs @@ -24,7 +24,7 @@ public async Task calls_subscription_dropped_when_disposed() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); using var subscription = await _fixture.Client - .SubscribeToAllAsync(Position.End, EventAppeared, false, SubscriptionDropped) + .SubscribeToAllAsync(FromAll.End, EventAppeared, false, SubscriptionDropped) .WithTimeout(); if (dropped.Task.IsCompleted) { @@ -51,7 +51,7 @@ public async Task calls_subscription_dropped_when_error_processing_event() { var expectedException = new Exception("Error"); using var subscription = await _fixture.Client - .SubscribeToAllAsync(Position.End, EventAppeared, false, SubscriptionDropped) + .SubscribeToAllAsync(FromAll.End, EventAppeared, false, SubscriptionDropped) .WithTimeout(); await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); @@ -74,7 +74,7 @@ public async Task subscribe_to_empty_database() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); using var subscription = await _fixture.Client - .SubscribeToAllAsync(Position.End, EventAppeared, false, SubscriptionDropped) + .SubscribeToAllAsync(FromAll.End, EventAppeared, false, SubscriptionDropped) .WithTimeout(); Assert.False(appeared.Task.IsCompleted); @@ -110,7 +110,7 @@ public async Task does_not_read_existing_events_but_keep_listening_to_new_ones() var afterEvents = _fixture.CreateTestEvents(10).ToArray(); using var subscription = await _fixture.Client - .SubscribeToAllAsync(Position.End, EventAppeared, false, SubscriptionDropped) + .SubscribeToAllAsync(FromAll.End, EventAppeared, false, SubscriptionDropped) .WithTimeout(); foreach (var @event in afterEvents) { 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 index 9aa3b163c..4b3141e14 100644 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_all_with_position.cs +++ b/test/EventStore.Client.Streams.Tests/subscribe_to_all_with_position.cs @@ -26,7 +26,8 @@ public async Task calls_subscription_dropped_when_disposed() { .FirstOrDefaultAsync(); using var subscription = await _fixture.Client - .SubscribeToAllAsync(firstEvent.OriginalEvent.Position, EventAppeared, false, SubscriptionDropped) + .SubscribeToAllAsync(FromAll.After(firstEvent.OriginalEvent.Position), EventAppeared, + false, SubscriptionDropped) .WithTimeout(); if (dropped.Task.IsCompleted) { @@ -56,7 +57,8 @@ public async Task calls_subscription_dropped_when_error_processing_event() { .FirstOrDefaultAsync(); using var subscription = await _fixture.Client - .SubscribeToAllAsync(firstEvent.OriginalEvent.Position, EventAppeared, false, SubscriptionDropped) + .SubscribeToAllAsync(FromAll.After(firstEvent.OriginalEvent.Position), EventAppeared, + false, SubscriptionDropped) .WithTimeout(); await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents(2)); @@ -82,7 +84,8 @@ public async Task subscribe_to_empty_database() { .FirstOrDefaultAsync(); using var subscription = await _fixture.Client - .SubscribeToAllAsync(firstEvent.OriginalEvent.Position, EventAppeared, false, SubscriptionDropped) + .SubscribeToAllAsync(FromAll.After(firstEvent.OriginalEvent.Position), EventAppeared, + false, SubscriptionDropped) .WithTimeout(); Assert.False(appeared.Task.IsCompleted); @@ -138,8 +141,8 @@ await _fixture.Client.AppendToStreamAsync($"stream-{@event.EventId:n}", StreamSt new[] {@event}); } - using var subscription = await _fixture.Client - .SubscribeToAllAsync(position, EventAppeared, false, SubscriptionDropped) + using var subscription = await _fixture.Client.SubscribeToAllAsync(FromAll.After(position), + EventAppeared, false, SubscriptionDropped) .WithTimeout(); foreach (var @event in afterEvents) { diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_stream.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_stream.cs index 939796d64..41b1105ae 100644 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_stream.cs +++ b/test/EventStore.Client.Streams.Tests/subscribe_to_stream.cs @@ -22,7 +22,8 @@ public async Task subscribe_to_non_existing_stream() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); using var subscription = await _fixture.Client - .SubscribeToStreamAsync(stream, EventAppeared, false, SubscriptionDropped) + .SubscribeToStreamAsync(stream, FromStream.Start, EventAppeared, false, + SubscriptionDropped) .WithTimeout(); Assert.False(appeared.Task.IsCompleted); @@ -53,7 +54,8 @@ public async Task subscribe_to_non_existing_stream_then_get_event() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); using var subscription = await _fixture.Client - .SubscribeToStreamAsync(stream, EventAppeared, false, SubscriptionDropped) + .SubscribeToStreamAsync(stream, FromStream.Start, EventAppeared, false, + SubscriptionDropped) .WithTimeout(); await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); @@ -88,8 +90,10 @@ public async Task allow_multiple_subscriptions_to_same_stream() { int appearedCount = 0; await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); - using var s1 = await _fixture.Client.SubscribeToStreamAsync(stream, EventAppeared).WithTimeout(); - using var s2 = await _fixture.Client.SubscribeToStreamAsync(stream, EventAppeared).WithTimeout(); + 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()); @@ -108,7 +112,8 @@ public async Task calls_subscription_dropped_when_disposed() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); using var subscription = await _fixture.Client - .SubscribeToStreamAsync(stream, EventAppeared, false, SubscriptionDropped) + .SubscribeToStreamAsync(stream, FromStream.Start, EventAppeared, false, + SubscriptionDropped) .WithTimeout(); if (dropped.Task.IsCompleted) { @@ -135,7 +140,8 @@ public async Task calls_subscription_dropped_when_error_processing_event() { var expectedException = new Exception("Error"); using var subscription = await _fixture.Client - .SubscribeToStreamAsync(stream, EventAppeared, false, SubscriptionDropped) + .SubscribeToStreamAsync(stream, FromStream.Start, EventAppeared, false, + SubscriptionDropped) .WithTimeout(); if (dropped.Task.IsCompleted) { @@ -176,7 +182,8 @@ await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, beforeEv .WithTimeout(); using var subscription = await _fixture.Client - .SubscribeToStreamAsync(stream, EventAppeared, false, SubscriptionDropped) + .SubscribeToStreamAsync(stream, FromStream.Start, EventAppeared, false, + SubscriptionDropped) .WithTimeout(); await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, afterEvents) @@ -216,7 +223,8 @@ public async Task catches_deletions() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); using var _ = await _fixture.Client - .SubscribeToStreamAsync(stream, EventAppeared, false, SubscriptionDropped) + .SubscribeToStreamAsync(stream, FromStream.Start, EventAppeared, false, + SubscriptionDropped) .WithTimeout(); await _fixture.Client.TombstoneAsync(stream, StreamState.NoStream); diff --git a/test/EventStore.Client.Streams.Tests/subscribe_to_stream_live.cs b/test/EventStore.Client.Streams.Tests/subscribe_to_stream_live.cs index 6deeedd27..578f5cd40 100644 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_stream_live.cs +++ b/test/EventStore.Client.Streams.Tests/subscribe_to_stream_live.cs @@ -24,7 +24,7 @@ await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); using var _ = await _fixture.Client - .SubscribeToStreamAsync(stream, StreamPosition.End, (s, e, ct) => { + .SubscribeToStreamAsync(stream, FromStream.End, (_, e, _) => { appeared.TrySetResult(e.OriginalEventNumber); return Task.CompletedTask; }, false, (s, reason, ex) => dropped.TrySetResult(true)) @@ -43,7 +43,7 @@ public async Task subscribe_to_non_existing_stream_and_then_catch_new_event() { var dropped = new TaskCompletionSource(); using var _ = await _fixture.Client - .SubscribeToStreamAsync(stream, StreamPosition.End, (s, e, ct) => { + .SubscribeToStreamAsync(stream, FromStream.End, (_, _, _) => { appeared.TrySetResult(true); return Task.CompletedTask; }, false, (s, reason, ex) => dropped.TrySetResult(true)) @@ -62,9 +62,11 @@ public async Task allow_multiple_subscriptions_to_same_stream() { int appearedCount = 0; - using var s1 = await _fixture.Client.SubscribeToStreamAsync(stream, StreamPosition.End, EventAppeared) + using var s1 = await _fixture.Client + .SubscribeToStreamAsync(stream, FromStream.End, EventAppeared) .WithTimeout(); - using var s2 = await _fixture.Client.SubscribeToStreamAsync(stream, StreamPosition.End, EventAppeared) + using var s2 = await _fixture.Client + .SubscribeToStreamAsync(stream, FromStream.End, EventAppeared) .WithTimeout(); await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); @@ -86,7 +88,8 @@ public async Task calls_subscription_dropped_when_disposed() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); using var _ = await _fixture.Client - .SubscribeToStreamAsync(stream, StreamPosition.End, EventAppeared, false, SubscriptionDropped) + .SubscribeToStreamAsync(stream, FromStream.End, EventAppeared, false, + SubscriptionDropped) .WithTimeout(); await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents()); @@ -111,7 +114,8 @@ public async Task catches_deletions() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); using var _ = await _fixture.Client - .SubscribeToStreamAsync(stream, StreamPosition.End, EventAppeared, false, SubscriptionDropped) + .SubscribeToStreamAsync(stream, FromStream.End, EventAppeared, false, + SubscriptionDropped) .WithTimeout(); await _fixture.Client.TombstoneAsync(stream, StreamState.NoStream); 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 index 10beeb02c..24ff54134 100644 --- a/test/EventStore.Client.Streams.Tests/subscribe_to_stream_with_revision.cs +++ b/test/EventStore.Client.Streams.Tests/subscribe_to_stream_with_revision.cs @@ -9,6 +9,7 @@ namespace EventStore.Client { [Trait("Category", "LongRunning")] public class subscribe_to_stream_with_revision : IAsyncLifetime { private readonly Fixture _fixture; + public subscribe_to_stream_with_revision(ITestOutputHelper outputHelper) { _fixture = new Fixture(); _fixture.CaptureLogs(outputHelper); @@ -21,7 +22,8 @@ public async Task subscribe_to_non_existing_stream() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); using var subscription = await _fixture.Client - .SubscribeToStreamAsync(stream, StreamPosition.Start, EventAppeared, false, SubscriptionDropped) + .SubscribeToStreamAsync(stream, FromStream.Start, EventAppeared, false, + SubscriptionDropped) .WithTimeout(); Assert.False(appeared.Task.IsCompleted); @@ -52,7 +54,8 @@ public async Task subscribe_to_non_existing_stream_then_get_event() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); using var subscription = await _fixture.Client - .SubscribeToStreamAsync(stream, StreamPosition.Start, EventAppeared, false, SubscriptionDropped) + .SubscribeToStreamAsync(stream, FromStream.After(StreamPosition.Start), EventAppeared, + false, SubscriptionDropped) .WithTimeout(); await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, @@ -93,10 +96,10 @@ public async Task allow_multiple_subscriptions_to_same_stream() { int appearedCount = 0; using var s1 = await _fixture.Client - .SubscribeToStreamAsync(stream, StreamPosition.Start, EventAppeared) + .SubscribeToStreamAsync(stream, FromStream.After(StreamPosition.Start), EventAppeared) .WithTimeout(); using var s2 = await _fixture.Client - .SubscribeToStreamAsync(stream, StreamPosition.Start, EventAppeared) + .SubscribeToStreamAsync(stream, FromStream.After(StreamPosition.Start), EventAppeared) .WithTimeout(); await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents(2)); @@ -123,7 +126,8 @@ public async Task calls_subscription_dropped_when_disposed() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception)>(); using var subscription = await _fixture.Client - .SubscribeToStreamAsync(stream, StreamPosition.Start, EventAppeared, false, SubscriptionDropped) + .SubscribeToStreamAsync(stream, FromStream.Start, EventAppeared, false, + SubscriptionDropped) .WithTimeout(); if (dropped.Task.IsCompleted) { @@ -151,8 +155,9 @@ public async Task calls_subscription_dropped_when_error_processing_event() { await _fixture.Client.AppendToStreamAsync(stream, StreamState.NoStream, _fixture.CreateTestEvents(2)); - using var subscription = await _fixture.Client.SubscribeToStreamAsync(stream, StreamPosition.Start, - EventAppeared, false, SubscriptionDropped) + using var subscription = await _fixture.Client.SubscribeToStreamAsync(stream, + FromStream.Start, + EventAppeared, false, SubscriptionDropped) .WithTimeout(); var (reason, ex) = await dropped.Task.WithTimeout(); @@ -188,7 +193,8 @@ public async Task reads_all_existing_events_and_keep_listening_to_new_ones() { await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, beforeEvents); using var subscription = await _fixture.Client - .SubscribeToStreamAsync(stream, StreamPosition.Start, EventAppeared, false, SubscriptionDropped) + .SubscribeToStreamAsync(stream, FromStream.After(StreamPosition.Start), EventAppeared, + false, SubscriptionDropped) .WithTimeout(); var writeResult = await _fixture.Client.AppendToStreamAsync(stream, StreamState.Any, afterEvents); diff --git a/test/EventStore.Client.Tests/FromAllTests.cs b/test/EventStore.Client.Tests/FromAllTests.cs new file mode 100644 index 000000000..505c5e089 --- /dev/null +++ b/test/EventStore.Client.Tests/FromAllTests.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using AutoFixture; +using Xunit; + +namespace EventStore.Client { + public class FromAllTests : ValueObjectTests { + public FromAllTests() : base(new ScenarioFixture()) { + } + + [Fact] + public void IsComparable() => + Assert.IsAssignableFrom>(_fixture.Create()); + + [Theory, AutoScenarioData(typeof(ScenarioFixture))] + public void StartIsLessThanAll(FromAll other) => Assert.True(FromAll.Start < other); + + [Theory, AutoScenarioData(typeof(ScenarioFixture))] + public void LiveIsGreaterThanAll(FromAll other) => Assert.True(FromAll.End > other); + + public static IEnumerable ToStringCases() { + var fixture = new ScenarioFixture(); + var position = fixture.Create(); + yield return new object[] {FromAll.After(position), position.ToString()}; + yield return new object[] {FromAll.Start, "Start"}; + yield return new object[] {FromAll.End, "Live"}; + } + + [Theory, MemberData(nameof(ToStringCases))] + public void ToStringReturnsExpectedResult(FromAll sut, string expected) => + Assert.Equal(expected, sut.ToString()); + + [Fact] + public void AfterLiveThrows() => + Assert.Throws(() => FromAll.After(Position.End)); + + [Fact] + public void ToUInt64ReturnsExpectedResults() { + var position = _fixture.Create(); + Assert.Equal((position.CommitPosition, position.PreparePosition), + FromAll.After(position).ToUInt64()); + } + + private class ScenarioFixture : Fixture { + public ScenarioFixture() { + Customize(composer => composer.FromFactory(value => new Position(value, value))); + Customize(composter => + composter.FromFactory(FromAll.After)); + } + } + } +} diff --git a/test/EventStore.Client.Tests/FromStreamTests.cs b/test/EventStore.Client.Tests/FromStreamTests.cs new file mode 100644 index 000000000..03ab4c398 --- /dev/null +++ b/test/EventStore.Client.Tests/FromStreamTests.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using AutoFixture; +using Xunit; + +namespace EventStore.Client { + public class FromStreamTests : ValueObjectTests { + public FromStreamTests() : base(new ScenarioFixture()) { + } + + [Fact] + public void IsComparable() => + Assert.IsAssignableFrom>( + _fixture.Create()); + + [Theory, AutoScenarioData(typeof(ScenarioFixture))] + public void StartIsLessThanAll(FromStream other) => + Assert.True(FromStream.Start < other); + + [Theory, AutoScenarioData(typeof(ScenarioFixture))] + public void LiveIsGreaterThanAll(FromStream other) => + Assert.True(FromStream.End > other); + + public static IEnumerable ToStringCases() { + var fixture = new ScenarioFixture(); + var position = fixture.Create(); + yield return new object[] {FromStream.After(position), position.ToString()}; + yield return new object[] {FromStream.Start, "Start"}; + yield return new object[] {FromStream.End, "Live"}; + } + + [Theory, MemberData(nameof(ToStringCases))] + public void ToStringReturnsExpectedResult(FromStream sut, string expected) => + Assert.Equal(expected, sut.ToString()); + + [Fact] + public void AfterLiveThrows() => + Assert.Throws(() => FromStream.After(StreamPosition.End)); + + [Fact] + public void ToUInt64ReturnsExpectedResults() { + var position = _fixture.Create(); + Assert.Equal(position.ToUInt64(), FromStream.After(position).ToUInt64()); + } + + private class ScenarioFixture : Fixture { + public ScenarioFixture() { + Customize(composer => composer.FromFactory(value => new StreamPosition(value))); + Customize(composter => + composter.FromFactory(FromStream.After)); + } + } + } +}