diff --git a/samples/appending-events/Program.cs b/samples/appending-events/Program.cs index a9fedf2a1..16936d536 100644 --- a/samples/appending-events/Program.cs +++ b/samples/appending-events/Program.cs @@ -1,6 +1,4 @@ -using KurrentDB.Client; - -#pragma warning disable CS8321 // Local function is declared but never used +#pragma warning disable CS8321 // Local function is declared but never used var settings = KurrentDBClientSettings.Create("esdb://localhost:2113?tls=false"); @@ -9,17 +7,19 @@ await using var client = new KurrentDBClient(settings); await AppendToStream(client); +await AppendToStreamWithAutoSerialization(client); +await AppendToStreamWithMetadataAndAutoSerialization(client); await AppendWithConcurrencyCheck(client); await AppendWithNoStream(client); await AppendWithSameId(client); +await AppendWithSameIdAndAutoSerialization(client); return; static async Task AppendToStream(KurrentDBClient client) { #region append-to-stream - var eventData = new EventData( - Uuid.NewUuid(), + var eventData = MessageData.From( "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); @@ -27,54 +27,120 @@ static async Task AppendToStream(KurrentDBClient client) { await client.AppendToStreamAsync( "some-stream", StreamState.NoStream, - new List { - eventData - } + [eventData] ); #endregion append-to-stream } +static async Task AppendToStreamWithAutoSerialization(KurrentDBClient client) { + #region append-to-stream-with-auto-serialization + + var shoppingCartId = Guid.NewGuid(); + + var @event = new ProductItemAddedToShoppingCart( + shoppingCartId, + new PricedProductItem("t-shirt", 1, 99.99m) + ); + + await client.AppendToStreamAsync( + $"shopping_cart-{shoppingCartId}", + StreamState.NoStream, + [@event] + ); + + #endregion append-to-stream-with-auto-serialization +} + +static async Task AppendToStreamWithMetadataAndAutoSerialization(KurrentDBClient client) { + #region append-to-stream-with-metadata-and-auto-serialization + + var shoppingCartId = Guid.NewGuid(); + var clientId = Guid.NewGuid().ToString(); + + var @event = new ProductItemAddedToShoppingCart( + shoppingCartId, + new PricedProductItem("t-shirt", 1, 99.99m) + ); + + var metadata = new ShoppingCartMetadata(clientId); + + var message = Message.From(@event, metadata); + + await client.AppendToStreamAsync( + $"shopping_cart-{shoppingCartId}", + StreamState.NoStream, + [message] + ); + + #endregion append-to-stream-with-metadata-and-auto-serialization +} + static async Task AppendWithSameId(KurrentDBClient client) { #region append-duplicate-event - var eventData = new EventData( - Uuid.NewUuid(), + var eventData = MessageData.From( "some-event", - "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() + "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray(), + messageId: Uuid.NewUuid() ); await client.AppendToStreamAsync( "same-event-stream", StreamState.Any, - new List { - eventData - } + [eventData] ); // attempt to append the same event again await client.AppendToStreamAsync( "same-event-stream", StreamState.Any, - new List { - eventData - } + [eventData] ); #endregion append-duplicate-event } +static async Task AppendWithSameIdAndAutoSerialization(KurrentDBClient client) { + #region append-duplicate-event-with-serialization + + var shoppingCartId = Guid.NewGuid(); + + var @event = new ProductItemAddedToShoppingCart( + shoppingCartId, + new PricedProductItem("t-shirt", 1, 99.99m) + ); + + var message = Message.From( + @event, + messageId: Uuid.NewUuid() + ); + + await client.AppendToStreamAsync( + "same-event-stream", + StreamState.Any, + [message] + ); + + // attempt to append the same event again + await client.AppendToStreamAsync( + "same-event-stream", + StreamState.Any, + [message] + ); + + #endregion append-duplicate-event-with-serialization +} + static async Task AppendWithNoStream(KurrentDBClient client) { #region append-with-no-stream - var eventDataOne = new EventData( - Uuid.NewUuid(), + var eventDataOne = MessageData.From( "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); - var eventDataTwo = new EventData( - Uuid.NewUuid(), + var eventDataTwo = MessageData.From( "some-event", "{\"id\": \"2\" \"value\": \"some other value\"}"u8.ToArray() ); @@ -82,18 +148,14 @@ static async Task AppendWithNoStream(KurrentDBClient client) { await client.AppendToStreamAsync( "no-stream-stream", StreamState.NoStream, - new List { - eventDataOne - } + [eventDataOne] ); // attempt to append the same event again await client.AppendToStreamAsync( "no-stream-stream", StreamState.NoStream, - new List { - eventDataTwo - } + [eventDataTwo] ); #endregion append-with-no-stream @@ -103,24 +165,19 @@ static async Task AppendWithConcurrencyCheck(KurrentDBClient client) { await client.AppendToStreamAsync( "concurrency-stream", StreamState.Any, - new[] { new EventData(Uuid.NewUuid(), "-", ReadOnlyMemory.Empty) } + [MessageData.From("-", ReadOnlyMemory.Empty)] ); #region append-with-concurrency-check - var clientOneRead = client.ReadStreamAsync( - Direction.Forwards, - "concurrency-stream", - StreamPosition.Start - ); + var clientOneRead = client.ReadStreamAsync("concurrency-stream"); var clientOneRevision = (await clientOneRead.LastAsync()).Event.EventNumber.ToUInt64(); - var clientTwoRead = client.ReadStreamAsync(Direction.Forwards, "concurrency-stream", StreamPosition.Start); + var clientTwoRead = client.ReadStreamAsync("concurrency-stream"); var clientTwoRevision = (await clientTwoRead.LastAsync()).Event.EventNumber.ToUInt64(); - var clientOneData = new EventData( - Uuid.NewUuid(), + var clientOneData = MessageData.From( "some-event", "{\"id\": \"1\" \"value\": \"clientOne\"}"u8.ToArray() ); @@ -128,13 +185,10 @@ await client.AppendToStreamAsync( await client.AppendToStreamAsync( "no-stream-stream", clientOneRevision, - new List { - clientOneData - } + [clientOneData] ); - var clientTwoData = new EventData( - Uuid.NewUuid(), + var clientTwoData = MessageData.From( "some-event", "{\"id\": \"2\" \"value\": \"clientTwo\"}"u8.ToArray() ); @@ -142,17 +196,14 @@ await client.AppendToStreamAsync( await client.AppendToStreamAsync( "no-stream-stream", clientTwoRevision, - new List { - clientTwoData - } + [clientTwoData] ); #endregion append-with-concurrency-check } static async Task AppendOverridingUserCredentials(KurrentDBClient client, CancellationToken cancellationToken) { - var eventData = new EventData( - Uuid.NewUuid(), + var eventData = MessageData.From( "TestEvent", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); @@ -162,10 +213,23 @@ static async Task AppendOverridingUserCredentials(KurrentDBClient client, Cancel await client.AppendToStreamAsync( "some-stream", StreamState.Any, - new[] { eventData }, - userCredentials: new UserCredentials("admin", "changeit"), - cancellationToken: cancellationToken + [eventData], + new AppendToStreamOptions { UserCredentials = new UserCredentials("admin", "changeit") }, + cancellationToken ); #endregion overriding-user-credentials } + +public record PricedProductItem( + string ProductId, + int Quantity, + decimal UnitPrice +); + +public record ProductItemAddedToShoppingCart( + Guid CartId, + PricedProductItem ProductItem +); + +public record ShoppingCartMetadata(string ClientId); diff --git a/samples/diagnostics/Program.cs b/samples/diagnostics/Program.cs index 0310858bf..5bdb96dcf 100644 --- a/samples/diagnostics/Program.cs +++ b/samples/diagnostics/Program.cs @@ -55,8 +55,7 @@ static async Task TraceAppendToStream(KurrentDBClient client) { host.Start(); - var eventData = new EventData( - Uuid.NewUuid(), + var eventData = MessageData.From( "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); @@ -64,9 +63,7 @@ static async Task TraceAppendToStream(KurrentDBClient client) { await client.AppendToStreamAsync( Uuid.NewUuid().ToString(), StreamState.Any, - new List { - eventData - } + [eventData] ); # endregion setup-client-for-tracing diff --git a/samples/projection-management/Program.cs b/samples/projection-management/Program.cs index a868dfb11..01b88c1b5 100644 --- a/samples/projection-management/Program.cs +++ b/samples/projection-management/Program.cs @@ -439,8 +439,7 @@ static async Task Populate(string connection, int numberOfEvents) { var client = new KurrentDBClient(settings); var messages = Enumerable.Range(0, numberOfEvents).Select( number => - new EventData( - Uuid.NewUuid(), + MessageData.From( "eventtype", Encoding.UTF8.GetBytes($@"{{ ""Id"":{number} }}") ) diff --git a/samples/quick-start/Program.cs b/samples/quick-start/Program.cs index 7b9588b75..bfb35f386 100644 --- a/samples/quick-start/Program.cs +++ b/samples/quick-start/Program.cs @@ -1,7 +1,4 @@ -using System.Text.Json; -using KurrentDB.Client; - -var tokenSource = new CancellationTokenSource(); +var tokenSource = new CancellationTokenSource(); var cancellationToken = tokenSource.Token; #region createClient @@ -21,12 +18,6 @@ ImportantData = "I wrote my first event!" }; -var eventData = new EventData( - Uuid.NewUuid(), - "TestEvent", - JsonSerializer.SerializeToUtf8Bytes(evt) -); - #endregion createEvent #region appendEvents @@ -34,7 +25,7 @@ await client.AppendToStreamAsync( "some-stream", StreamState.Any, - new[] { eventData }, + [evt], cancellationToken: cancellationToken ); @@ -45,9 +36,9 @@ await client.AppendToStreamAsync( await client.AppendToStreamAsync( "some-stream", StreamState.Any, - new[] { eventData }, - userCredentials: new UserCredentials("admin", "changeit"), - cancellationToken: cancellationToken + [evt], + new AppendToStreamOptions { UserCredentials = new UserCredentials("admin", "changeit") }, + cancellationToken ); #endregion overriding-user-credentials @@ -55,13 +46,13 @@ await client.AppendToStreamAsync( #region readStream var result = client.ReadStreamAsync( - Direction.Forwards, "some-stream", - StreamPosition.Start, cancellationToken: cancellationToken ); -var events = await result.ToListAsync(cancellationToken); +var events = await result + .DeserializedData() + .ToListAsync(cancellationToken); #endregion readStream diff --git a/samples/reading-events/Program.cs b/samples/reading-events/Program.cs index 394fe5ee3..71be81186 100644 --- a/samples/reading-events/Program.cs +++ b/samples/reading-events/Program.cs @@ -1,16 +1,13 @@ -using KurrentDB.Client; - -#pragma warning disable CS8321 // Local function is declared but never used +#pragma warning disable CS8321 // Local function is declared but never used await using var client = new KurrentDBClient(KurrentDBClientSettings.Create("esdb://localhost:2113?tls=false")); var events = Enumerable.Range(0, 20) .Select( - r => new EventData( - Uuid.NewUuid(), - "some-event", - Encoding.UTF8.GetBytes($"{{\"id\": \"{r}\" \"value\": \"some value\"}}") - ) + _ => new TestEvent { + EntityId = Guid.NewGuid().ToString(), + ImportantData = Guid.NewGuid().ToString() + } ); await client.AppendToStreamAsync( @@ -20,23 +17,21 @@ await client.AppendToStreamAsync( ); await ReadFromStream(client); +await ReadFromStreamWithDisabledAutoSerialization(client); return; static async Task ReadFromStream(KurrentDBClient client) { #region read-from-stream - var events = client.ReadStreamAsync( - Direction.Forwards, - "some-stream", - StreamPosition.Start - ); + var events = client.ReadStreamAsync("some-stream"); #endregion read-from-stream #region iterate-stream - await foreach (var @event in events) Console.WriteLine(Encoding.UTF8.GetString(@event.Event.Data.ToArray())); + await foreach (var @event in events) + Console.WriteLine(@event.DeserializedData); #endregion iterate-stream @@ -48,20 +43,32 @@ static async Task ReadFromStream(KurrentDBClient client) { #endregion } +static async Task ReadFromStreamWithDisabledAutoSerialization(KurrentDBClient client) { + #region read-from-stream-with-disabled-auto-serialization + + var events = client.ReadStreamAsync("some-stream", new ReadStreamOptions().DisableAutoSerialization()); + + #endregion read-from-stream-with-disabled-auto-serialization + + #region iterate-stream-with-disabled-auto-serialization + + await foreach (var resolvedEvent in events) + Console.WriteLine(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span)); + + #endregion iterate-stream-with-disabled-auto-serialization +} + static async Task ReadFromStreamMessages(KurrentDBClient client) { #region read-from-stream-messages - var streamPosition = StreamPosition.Start; - var results = client.ReadStreamAsync( - Direction.Forwards, - "some-stream", - streamPosition - ); + var results = client.ReadStreamAsync("some-stream"); #endregion read-from-stream-messages #region iterate-stream-messages + var streamPosition = StreamPosition.Start; + await foreach (var message in results.Messages) switch (message) { case StreamMessage.Ok ok: @@ -73,7 +80,7 @@ static async Task ReadFromStreamMessages(KurrentDBClient client) { return; case StreamMessage.Event(var resolvedEvent): - Console.WriteLine(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span)); + Console.WriteLine(resolvedEvent.DeserializedData); break; case StreamMessage.FirstStreamPosition(var sp): @@ -96,17 +103,15 @@ static async Task ReadFromStreamPosition(KurrentDBClient client) { #region read-from-stream-position var events = client.ReadStreamAsync( - Direction.Forwards, "some-stream", - 10, - 20 + new ReadStreamOptions { StreamPosition = 10, MaxCount = 20 } ); #endregion read-from-stream-position #region iterate-stream - await foreach (var @event in events) Console.WriteLine(Encoding.UTF8.GetString(@event.Event.Data.ToArray())); + await foreach (var @event in events) Console.WriteLine(@event.DeserializedData); #endregion iterate-stream } @@ -115,10 +120,8 @@ static async Task ReadFromStreamPositionCheck(KurrentDBClient client) { #region checking-for-stream-presence var result = client.ReadStreamAsync( - Direction.Forwards, "some-stream", - 10, - 20 + new ReadStreamOptions { StreamPosition = 10, MaxCount = 20 } ); if (await result.ReadState == ReadState.StreamNotFound) return; @@ -132,9 +135,8 @@ static async Task ReadFromStreamBackwards(KurrentDBClient client) { #region reading-backwards var events = client.ReadStreamAsync( - Direction.Backwards, "some-stream", - StreamPosition.End + new ReadStreamOptions { Direction = Direction.Backwards, StreamPosition = StreamPosition.End } ); await foreach (var e in events) Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); @@ -146,9 +148,8 @@ static async Task ReadFromStreamMessagesBackwards(KurrentDBClient client) { #region read-from-stream-messages-backwards var results = client.ReadStreamAsync( - Direction.Forwards, "some-stream", - StreamPosition.End + new ReadStreamOptions { Direction = Direction.Backwards, StreamPosition = StreamPosition.End } ); #endregion read-from-stream-messages-backwards @@ -180,13 +181,13 @@ static async Task ReadFromStreamMessagesBackwards(KurrentDBClient client) { static async Task ReadFromAllStream(KurrentDBClient client) { #region read-from-all-stream - var events = client.ReadAllAsync(Direction.Forwards, Position.Start); + var events = client.ReadAllAsync(); #endregion read-from-all-stream #region read-from-all-stream-iterate - await foreach (var e in events) Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); + await foreach (var e in events) Console.WriteLine(e.DeserializedData); #endregion read-from-all-stream-iterate } @@ -195,10 +196,7 @@ static async Task ReadFromAllStreamMessages(KurrentDBClient client) { #region read-from-all-stream-messages var position = Position.Start; - var results = client.ReadAllAsync( - Direction.Forwards, - position - ); + var results = client.ReadAllAsync(new ReadAllOptions { Position = position }); #endregion read-from-all-stream-messages @@ -221,12 +219,12 @@ static async Task ReadFromAllStreamMessages(KurrentDBClient client) { static async Task IgnoreSystemEvents(KurrentDBClient client) { #region ignore-system-events - var events = client.ReadAllAsync(Direction.Forwards, Position.Start); + var events = client.ReadAllAsync(); await foreach (var e in events) { if (e.Event.EventType.StartsWith("$")) continue; - Console.WriteLine(Encoding.UTF8.GetString(e.Event.Data.ToArray())); + Console.WriteLine(e.DeserializedData); } #endregion ignore-system-events @@ -235,7 +233,7 @@ static async Task IgnoreSystemEvents(KurrentDBClient client) { static async Task ReadFromAllStreamBackwards(KurrentDBClient client) { #region read-from-all-stream-backwards - var events = client.ReadAllAsync(Direction.Backwards, Position.End); + var events = client.ReadAllAsync(new ReadAllOptions { Direction = Direction.Backwards, Position = Position.End }); #endregion read-from-all-stream-backwards @@ -250,10 +248,7 @@ static async Task ReadFromAllStreamBackwardsMessages(KurrentDBClient client) { #region read-from-all-stream-messages-backwards var position = Position.End; - var results = client.ReadAllAsync( - Direction.Backwards, - position - ); + var results = client.ReadAllAsync(new ReadAllOptions { Direction = Direction.Backwards, Position = position }); #endregion read-from-all-stream-messages-backwards @@ -275,7 +270,7 @@ static async Task ReadFromAllStreamBackwardsMessages(KurrentDBClient client) { } static async Task FilteringOutSystemEvents(KurrentDBClient client) { - var events = client.ReadAllAsync(Direction.Forwards, Position.Start); + var events = client.ReadAllAsync(); await foreach (var e in events) if (!e.Event.EventType.StartsWith("$")) @@ -286,10 +281,8 @@ static void ReadStreamOverridingUserCredentials(KurrentDBClient client, Cancella #region overriding-user-credentials var result = client.ReadStreamAsync( - Direction.Forwards, "some-stream", - StreamPosition.Start, - userCredentials: new UserCredentials("admin", "changeit"), + new ReadStreamOptions { UserCredentials = new UserCredentials("admin", "changeit") }, cancellationToken: cancellationToken ); @@ -300,9 +293,7 @@ static void ReadAllOverridingUserCredentials(KurrentDBClient client, Cancellatio #region read-all-overriding-user-credentials var result = client.ReadAllAsync( - Direction.Forwards, - Position.Start, - userCredentials: new UserCredentials("admin", "changeit"), + new ReadAllOptions { UserCredentials = new UserCredentials("admin", "changeit") }, cancellationToken: cancellationToken ); @@ -313,11 +304,14 @@ static void ReadAllResolvingLinkTos(KurrentDBClient client, CancellationToken ca #region read-from-all-stream-resolving-link-Tos var result = client.ReadAllAsync( - Direction.Forwards, - Position.Start, - resolveLinkTos: true, + new ReadAllOptions { ResolveLinkTos = true }, cancellationToken: cancellationToken ); #endregion read-from-all-stream-resolving-link-Tos } + +public class TestEvent { + public string? EntityId { get; set; } + public string? ImportantData { get; set; } +} diff --git a/samples/secure-with-tls/Program.cs b/samples/secure-with-tls/Program.cs index e82f1f548..82de15db5 100644 --- a/samples/secure-with-tls/Program.cs +++ b/samples/secure-with-tls/Program.cs @@ -9,11 +9,11 @@ await using var client = new KurrentDBClient(KurrentDBClientSettings.Create(connectionString)); -var eventData = new EventData(Uuid.NewUuid(), "some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray()); +var eventData = MessageData.From("some-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray()); try { var appendResult = await client.AppendToStreamAsync( - "some-stream", StreamState.Any, new List { eventData } + "some-stream", StreamState.Any, [eventData] ); Console.WriteLine($"SUCCESS! Append result: {appendResult.LogPosition}"); diff --git a/samples/server-side-filtering/Program.cs b/samples/server-side-filtering/Program.cs index 720d5c2f5..1accf0621 100644 --- a/samples/server-side-filtering/Program.cs +++ b/samples/server-side-filtering/Program.cs @@ -1,6 +1,5 @@ #pragma warning disable CS8321 // Local function is declared but never used -using KurrentDB.Client; using EventTypeFilter = KurrentDB.Client.EventTypeFilter; const int eventCount = 100; @@ -9,29 +8,31 @@ await using var client = new KurrentDBClient(KurrentDBClientSettings.Create("esdb://localhost:2113?tls=false")); -_ = Task.Run(async () => { - await using var subscription = client.SubscribeToAll( - FromAll.Start, - filterOptions: new SubscriptionFilterOptions(EventTypeFilter.Prefix("some-"))); - await foreach (var message in subscription.Messages) { - switch (message) { - case StreamMessage.Event(var e): - Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); - semaphore.Release(); - break; - case StreamMessage.AllStreamCheckpointReached(var p): - Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); - break; +_ = Task.Run( + async () => { + await using var subscription = client.SubscribeToAll( + new SubscribeToAllOptions { FilterOptions = new SubscriptionFilterOptions(EventTypeFilter.Prefix("some-")) } + ); + + await foreach (var message in subscription.Messages) { + switch (message) { + case StreamMessage.Event(var e): + Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); + semaphore.Release(); + break; + + case StreamMessage.AllStreamCheckpointReached(var p): + Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); + break; + } } } -}); - +); await Task.Delay(2000); for (var i = 0; i < eventCount; i++) { - var eventData = new EventData( - Uuid.NewUuid(), + var eventData = MessageData.From( i % 2 == 0 ? "some-event" : "other-event", "{\"id\": \"1\" \"value\": \"some value\"}"u8.ToArray() ); @@ -39,7 +40,7 @@ await client.AppendToStreamAsync( Guid.NewGuid().ToString("N"), StreamState.Any, - new List { eventData } + [eventData] ); } @@ -51,8 +52,11 @@ static async Task ExcludeSystemEvents(KurrentDBClient client) { #region exclude-system await using var subscription = client.SubscribeToAll( - FromAll.Start, - filterOptions: new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents())); + new SubscribeToAllOptions { + FilterOptions = new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents()) + } + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): @@ -71,7 +75,7 @@ static async Task EventTypePrefix(KurrentDBClient client) { #endregion event-type-prefix - await using var subscription = client.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = client.SubscribeToAll(new SubscribeToAllOptions { FilterOptions = filterOptions }); await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): @@ -88,7 +92,7 @@ static async Task EventTypeRegex(KurrentDBClient client) { #endregion event-type-regex - await using var subscription = client.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = client.SubscribeToAll(new SubscribeToAllOptions { FilterOptions = filterOptions }); await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): @@ -105,7 +109,7 @@ static async Task StreamPrefix(KurrentDBClient client) { #endregion stream-prefix - await using var subscription = client.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = client.SubscribeToAll(new SubscribeToAllOptions { FilterOptions = filterOptions }); await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): @@ -122,7 +126,7 @@ static async Task StreamRegex(KurrentDBClient client) { #endregion stream-regex - await using var subscription = client.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = client.SubscribeToAll(new SubscribeToAllOptions { FilterOptions = filterOptions }); await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): @@ -137,18 +141,19 @@ static async Task CheckpointCallback(KurrentDBClient client) { var filterOptions = new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents()); - await using var subscription = client.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = client.SubscribeToAll(new SubscribeToAllOptions { FilterOptions = filterOptions }); await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); break; + case StreamMessage.AllStreamCheckpointReached(var p): Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); break; } } - + #endregion checkpoint } @@ -159,12 +164,13 @@ static async Task CheckpointCallbackWithInterval(KurrentDBClient client) { #endregion checkpoint-with-interval - await using var subscription = client.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = client.SubscribeToAll(new SubscribeToAllOptions { FilterOptions = filterOptions }); await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var e): Console.WriteLine($"{e.Event.EventType} @ {e.Event.Position.PreparePosition}"); break; + case StreamMessage.AllStreamCheckpointReached(var p): Console.WriteLine($"checkpoint taken at {p.PreparePosition}"); break; diff --git a/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs b/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs index 1773873ff..a3cd6330d 100644 --- a/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs +++ b/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs @@ -14,7 +14,7 @@ public EventStoreController(KurrentDBClient KurrentDBClient) { [HttpGet] public IAsyncEnumerable Get() { - return _KurrentClient.ReadAllAsync(Direction.Forwards, Position.Start); + return _KurrentClient.ReadAllAsync(); } #endregion using-dependency } diff --git a/samples/subscribing-to-streams/Program.cs b/samples/subscribing-to-streams/Program.cs index f27f07efd..7b14e70a4 100644 --- a/samples/subscribing-to-streams/Program.cs +++ b/samples/subscribing-to-streams/Program.cs @@ -1,18 +1,19 @@ -using KurrentDB.Client; - -#pragma warning disable CS8321 // Local function is declared but never used +#pragma warning disable CS8321 // Local function is declared but never used // ReSharper disable UnusedParameter.Local // ReSharper disable UnusedVariable await using var client = new KurrentDBClient(KurrentDBClientSettings.Create("esdb://localhost:2113?tls=false")); -await Task.WhenAll(YieldSamples().Select(async sample => { - try { - await sample; - } catch (OperationCanceledException) { } -})); - +await Task.WhenAll( + YieldSamples().Select( + async sample => { + try { + await sample; + } catch (OperationCanceledException) { } + } + ) +); return; @@ -34,8 +35,12 @@ static async Task SubscribeToStreamFromPosition(KurrentDBClient client, Cancella await using var subscription = client.SubscribeToStream( "some-stream", - FromStream.After(StreamPosition.FromInt64(20)), - cancellationToken: ct); + new SubscribeToStreamOptions { + Start = FromStream.After(StreamPosition.FromInt64(20)) + }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -53,8 +58,10 @@ static async Task SubscribeToStreamLive(KurrentDBClient client, CancellationToke await using var subscription = client.SubscribeToStream( "some-stream", - FromStream.End, - cancellationToken: ct); + new SubscribeToStreamOptions { Start = FromStream.End }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -72,9 +79,10 @@ static async Task SubscribeToStreamResolvingLinkTos(KurrentDBClient client, Canc await using var subscription = client.SubscribeToStream( "$et-myEventType", - FromStream.Start, - true, - cancellationToken: ct); + new SubscribeToStreamOptions { ResolveLinkTos = true }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -91,7 +99,7 @@ static async Task SubscribeToStreamSubscriptionDropped(KurrentDBClient client, C #region subscribe-to-stream-subscription-dropped var checkpoint = await ReadStreamCheckpointAsync() switch { - null => FromStream.Start, + null => FromStream.Start, var position => FromStream.After(position.Value) }; @@ -99,8 +107,10 @@ static async Task SubscribeToStreamSubscriptionDropped(KurrentDBClient client, C try { await using var subscription = client.SubscribeToStream( "some-stream", - checkpoint, - cancellationToken: ct); + new SubscribeToStreamOptions { Start = checkpoint }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -125,10 +135,8 @@ static async Task SubscribeToStreamSubscriptionDropped(KurrentDBClient client, C static async Task SubscribeToStream(KurrentDBClient client, CancellationToken ct) { #region subscribe-to-stream - await using var subscription = client.SubscribeToStream( - "some-stream", - FromStream.Start, - cancellationToken: ct); + await using var subscription = client.SubscribeToStream("some-stream", cancellationToken: ct); + await foreach (var message in subscription.Messages.WithCancellation(ct)) { switch (message) { case StreamMessage.Event(var evnt): @@ -144,9 +152,8 @@ static async Task SubscribeToStream(KurrentDBClient client, CancellationToken ct static async Task SubscribeToAll(KurrentDBClient client, CancellationToken ct) { #region subscribe-to-all - await using var subscription = client.SubscribeToAll( - FromAll.Start, - cancellationToken: ct); + await using var subscription = client.SubscribeToAll(cancellationToken: ct); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -165,13 +172,16 @@ static async Task SubscribeToAllFromPosition(KurrentDBClient client, Cancellatio var result = await client.AppendToStreamAsync( "subscribe-to-all-from-position", StreamState.NoStream, - new[] { - new EventData(Uuid.NewUuid(), "-", ReadOnlyMemory.Empty) - }); + [MessageData.From("-", ReadOnlyMemory.Empty)] + ); await using var subscription = client.SubscribeToAll( - FromAll.After(result.LogPosition), - cancellationToken: ct); + new SubscribeToAllOptions { + Start = FromAll.After(result.LogPosition) + }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -188,8 +198,12 @@ static async Task SubscribeToAllLive(KurrentDBClient client, CancellationToken c #region subscribe-to-all-live var subscription = client.SubscribeToAll( - FromAll.End, - cancellationToken: ct); + new SubscribeToAllOptions { + Start = FromAll.End + }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -206,15 +220,17 @@ static async Task SubscribeToAllSubscriptionDropped(KurrentDBClient client, Canc #region subscribe-to-all-subscription-dropped var checkpoint = await ReadCheckpointAsync() switch { - null => FromAll.Start, + null => FromAll.Start, var position => FromAll.After(position.Value) }; Subscribe: try { await using var subscription = client.SubscribeToAll( - checkpoint, - cancellationToken: ct); + new SubscribeToAllOptions { Start = checkpoint }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -244,9 +260,10 @@ static async Task SubscribeToFiltered(KurrentDBClient client, CancellationToken var prefixStreamFilter = new SubscriptionFilterOptions(StreamFilter.Prefix("test-", "other-")); await using var subscription = client.SubscribeToAll( - FromAll.Start, - filterOptions: prefixStreamFilter, - cancellationToken: ct); + new SubscribeToAllOptions { FilterOptions = prefixStreamFilter }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): @@ -254,6 +271,7 @@ static async Task SubscribeToFiltered(KurrentDBClient client, CancellationToken await HandleEvent(evnt); break; + case StreamMessage.AllStreamCheckpointReached(var position): Console.WriteLine($"Checkpoint reached: {position}"); break; @@ -273,9 +291,10 @@ static async Task OverridingUserCredentials(KurrentDBClient client, Cancellation #region overriding-user-credentials await using var subscription = client.SubscribeToAll( - FromAll.Start, - userCredentials: new UserCredentials("admin", "changeit"), - cancellationToken: ct); + new SubscribeToAllOptions { UserCredentials = new UserCredentials("admin", "changeit") }, + cancellationToken: ct + ); + await foreach (var message in subscription.Messages) { switch (message) { case StreamMessage.Event(var evnt): diff --git a/src/KurrentDB.Client/Core/EventData.cs b/src/KurrentDB.Client/Core/EventData.cs index 5568ed11b..e02022c7d 100644 --- a/src/KurrentDB.Client/Core/EventData.cs +++ b/src/KurrentDB.Client/Core/EventData.cs @@ -1,65 +1,126 @@ -using System; using System.Net.Http.Headers; -namespace KurrentDB.Client { +namespace KurrentDB.Client; + +/// +/// Represents an event to be written. +/// +[Obsolete("Use MessageData instead.", false)] +public sealed class EventData { /// - /// Represents an event to be written. + /// The raw bytes of the event data. /// - public sealed class EventData { - /// - /// The raw bytes of the event data. - /// - public readonly ReadOnlyMemory Data; - - /// - /// The of the event, used as part of the idempotent write check. - /// - public readonly Uuid EventId; + public readonly ReadOnlyMemory Data; - /// - /// The raw bytes of the event metadata. - /// - public readonly ReadOnlyMemory Metadata; + /// + /// The of the event, used as part of the idempotent write check. + /// + public readonly Uuid EventId; - /// - /// The name of the event type. It is strongly recommended that these - /// use lowerCamelCase if projections are to be used. - /// - public readonly string Type; + /// + /// The raw bytes of the event metadata. + /// + public readonly ReadOnlyMemory Metadata; - /// - /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. - /// - public readonly string ContentType; + /// + /// The name of the event type. It is strongly recommended that these + /// use lowerCamelCase if projections are to be used. + /// + public readonly string Type; - /// - /// Constructs a new . - /// - /// The of the event, used as part of the idempotent write check. - /// The name of the event type. It is strongly recommended that these use lowerCamelCase if projections are to be used. - /// The raw bytes of the event data. - /// The raw bytes of the event metadata. - /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. - /// - public EventData(Uuid eventId, string type, ReadOnlyMemory data, ReadOnlyMemory? metadata = null, - string contentType = Constants.Metadata.ContentTypes.ApplicationJson) { - if (eventId == Uuid.Empty) { - throw new ArgumentOutOfRangeException(nameof(eventId)); - } + /// + /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. + /// + public readonly string ContentType; - MediaTypeHeaderValue.Parse(contentType); + /// + /// Constructs a new . + /// + /// The of the event, used as part of the idempotent write check. + /// The name of the event type. It is strongly recommended that these use lowerCamelCase if projections are to be used. + /// The raw bytes of the event data. + /// The raw bytes of the event metadata. + /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. + /// + public EventData( + Uuid eventId, string type, ReadOnlyMemory data, ReadOnlyMemory? metadata = null, + string contentType = Constants.Metadata.ContentTypes.ApplicationJson + ) { + if (eventId == Uuid.Empty) { + throw new ArgumentOutOfRangeException(nameof(eventId)); + } - if (contentType != Constants.Metadata.ContentTypes.ApplicationJson && - contentType != Constants.Metadata.ContentTypes.ApplicationOctetStream) { - throw new ArgumentOutOfRangeException(nameof(contentType), contentType, - $"Only {Constants.Metadata.ContentTypes.ApplicationJson} or {Constants.Metadata.ContentTypes.ApplicationOctetStream} are acceptable values."); - } + MediaTypeHeaderValue.Parse(contentType); - EventId = eventId; - Type = type; - Data = data; - Metadata = metadata ?? Array.Empty(); - ContentType = contentType; + if (contentType != Constants.Metadata.ContentTypes.ApplicationJson && + contentType != Constants.Metadata.ContentTypes.ApplicationOctetStream) { + throw new ArgumentOutOfRangeException( + nameof(contentType), + contentType, + $"Only {Constants.Metadata.ContentTypes.ApplicationJson} or {Constants.Metadata.ContentTypes.ApplicationOctetStream} are acceptable values." + ); } + + EventId = eventId; + Type = type; + Data = data; + Metadata = metadata ?? Array.Empty(); + ContentType = contentType; } + + /// + /// Creates a new with the specified event type, id, binary data and metadata + /// + /// The name of the event type. It is strongly recommended that these use lowerCamelCase if projections are to be used. + /// The raw bytes of the event data. + /// Optional metadata providing additional context about the event, such as correlation IDs, timestamps, or user information. + /// Unique identifier for this specific event instance. + /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. + /// A new immutable EventData instance with the specified properties. + /// Thrown when eventId is explicitly set to Uuid.Empty, which is an invalid identifier. + /// + /// + /// // Create an event with data and metadata + /// var orderPlaced = new OrderPlaced { OrderId = "ORD-123", Amount = 99.99m }; + /// + /// var metadata = new EventMetadata { + /// UserId = "user-456", + /// Timestamp = DateTimeOffset.UtcNow, + /// CorrelationId = correlationId + /// }; + /// + /// var type = typeof(OrderPlaced).FullName!; + /// var dataBytes = JsonSerializer.SerializeToUtf8Bytes(orderPlaced); + /// var metadataBytes = JsonSerializer.SerializeToUtf8Bytes(metadata); + /// + /// // Let the system assign an ID automatically + /// var event = EventData.From(type, dataBytes, metadataBytes); + /// + /// // Or specify a custom ID + /// var messageWithId = EventData.From(type, dataBytes, metadataBytes, Uuid.NewUuid()); + /// + /// + public static EventData From( + string type, + ReadOnlyMemory data, + ReadOnlyMemory? metadata = null, + Uuid? eventId = null, + string contentType = Constants.Metadata.ContentTypes.ApplicationJson + ) => + new(eventId ?? Uuid.NewUuid(), type, data, metadata, contentType); + + /// + /// Implicitly converts an instance to a instance. + /// The EventId becomes the MessageId, and all other properties are directly mapped. + /// + /// The event data to convert to message data. + /// A new instance with properties copied from the event data. + public static implicit operator MessageData(EventData eventData) => + new MessageData( + eventData.Type, + eventData.Data, + eventData.Metadata, + eventData.EventId, + eventData.ContentType + ); } diff --git a/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs b/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs index d894f5df2..e9e286a40 100644 --- a/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs +++ b/src/KurrentDB.Client/Core/KurrentDBClientSerializationSettings.cs @@ -38,23 +38,7 @@ public class KurrentDBClientSerializationSettings { /// public IMessageTypeNamingStrategy? MessageTypeNamingStrategy { get; set; } - /// - /// Allows to register mapping of CLR message types to their corresponding message type names used in serialized messages. - /// - public IDictionary MessageTypeMap { get; set; } = new Dictionary(); - - /// - /// Registers CLR message types that can be appended to the specific stream category. - /// Types will have message type names resolved based on the used - /// - public IDictionary CategoryMessageTypesMap { get; set; } = new Dictionary(); - - /// - /// Specifies the CLR type that should be used when deserializing metadata for all events. - /// When set, the client will attempt to deserialize event metadata into this type. - /// If not provided, will be used. - /// - public Type? DefaultMetadataType { get; set; } + public MessageTypeMappingSettings MessageTypeMapping { get; set; } = new MessageTypeMappingSettings(); /// /// Creates a new instance of serialization settings with either default values or custom configuration. @@ -65,13 +49,13 @@ public class KurrentDBClientSerializationSettings { /// /// /// var settings = KurrentDBClientSerializationSettings.Default(options => { - /// options.RegisterMessageType<UserCreated>("user-created"); - /// options.RegisterMessageType<UserUpdated>("user-updated"); - /// options.RegisterMessageTypeForCategory<UserCreated>("user"); + /// options.MessageTypeMapping.Register<UserRegistered>("user_registered"); + /// options.MessageTypeMapping.Register<RoleAssigned>("role_assigned"); + /// options.MessageTypeMapping.RegisterForCategory<UserRegistered>("user_onboarding"); /// }); /// /// - public static KurrentDBClientSerializationSettings Default( + public static KurrentDBClientSerializationSettings Get( Action? configure = null ) { var settings = new KurrentDBClientSerializationSettings(); @@ -180,6 +164,154 @@ IMessageTypeNamingStrategy messageTypeNamingStrategy return this; } + /// + /// Configures which serialization format (JSON or binary) is used by default when writing messages + /// where the content type isn't explicitly specified. The default content type is "application/json" + /// + /// The serialization format content type + /// The current instance for method chaining. + public KurrentDBClientSerializationSettings UseContentType(ContentType contentType) { + DefaultContentType = contentType; + + return this; + } + + /// + /// Allows to configure message type mapping + /// + /// + /// The current instance for method chaining. + public KurrentDBClientSerializationSettings ConfigureTypeMap(Action configure) { + configure(MessageTypeMapping); + + return this; + } + + /// + /// Creates a deep copy of the current serialization settings. + /// + /// A new instance with copied settings. + internal KurrentDBClientSerializationSettings Clone() { + return new KurrentDBClientSerializationSettings { + BytesSerializer = BytesSerializer, + JsonSerializer = JsonSerializer, + DefaultContentType = DefaultContentType, + MessageTypeMapping = MessageTypeMapping.Clone(), + MessageTypeNamingStrategy = MessageTypeNamingStrategy + }; + } +} + +/// +/// Provides operation-specific serialization settings that override the global client configuration +/// for individual operations like reading from or appending to streams. This allows fine-tuning +/// serialization behavior on a per-operation basis without changing the client-wide settings. +/// +public class OperationSerializationSettings { + /// + /// Controls whether mes should be automatically deserialized for this specific operation. + /// When enabled (the default), messages will be converted to their appropriate CLR types. + /// When disabled, messages will be returned in their raw serialized form. + /// + public AutomaticDeserialization AutomaticDeserialization { get; private set; } = AutomaticDeserialization.Enabled; + + /// + /// A callback that allows customizing serialization settings for this specific operation. + /// This can be used to override type mappings, serializers, or other settings just for + /// the scope of a single operation without affecting other operations. + /// + public Action? ConfigureSettings { get; private set; } + + /// + /// A pre-configured settings instance that disables automatic deserialization. + /// Use this when you need to access raw message data in its serialized form. + /// + public static readonly OperationSerializationSettings Disabled = new OperationSerializationSettings { + AutomaticDeserialization = AutomaticDeserialization.Disabled + }; + + /// + /// Creates operation-specific serialization settings with custom configuration while keeping + /// automatic deserialization enabled. This allows operation-specific type mappings or + /// serializer settings without changing the global client configuration. + /// + /// A callback to customize serialization settings for this operation. + /// A configured instance of with enabled deserialization. + public static OperationSerializationSettings Configure(Action configure) => + new OperationSerializationSettings { + AutomaticDeserialization = AutomaticDeserialization.Enabled, + ConfigureSettings = configure + }; +} + +/// +/// Controls whether the KurrentDB client should automatically deserialize message payloads +/// into their corresponding CLR types based on the configured type mappings. +/// +public enum AutomaticDeserialization { + /// + /// Disables automatic deserialization. Messages will be returned in their raw serialized form, + /// requiring manual deserialization by the application. Use this when you need direct access to the raw data + /// or when working with messages that don't have registered type mappings. + /// + Disabled = 0, + + /// + /// Enables automatic deserialization. The client will attempt to convert messages into their appropriate + /// CLR types using the configured serializers and type mappings. This simplifies working with strongly-typed + /// domain messages but requires proper type registration. + /// + Enabled = 1 +} + +/// +/// Controls whether the KurrentDB client should automatically register +/// message type names based on the CLR type using naming convention. +/// By default, it's enabled. +/// +public enum AutomaticTypeMappingRegistration { + /// + /// Enables automatic type registration. + /// The messages will be automatically discovered and resolved using registered naming resolution strategy. + /// + Enabled = 0, + + /// + /// Disables automatic type registration. If you use this setting, you need to register all type mappings manually. + /// If the type mapping is not registered for the specific message, the exception will be thrown. + /// + Disabled = 1 +} + +/// +/// Represents message type mapping settings +/// +public class MessageTypeMappingSettings { + /// + /// Allows to register mapping of CLR message types to their corresponding message type names used in serialized messages. + /// + public IDictionary TypeMap { get; set; } = new Dictionary(); + + /// + /// Registers CLR message types that can be appended to the specific stream category. + /// Types will have message type names resolved based on the used + /// + internal IDictionary CategoryTypesMap { get; set; } = new Dictionary(); + + /// + /// Specifies the CLR type that should be used when deserializing metadata for all events. + /// When set, the client will attempt to deserialize event metadata into this type. + /// If not provided, will be used. + /// + public Type? DefaultMetadataType { get; set; } + + /// + /// Controls whether the KurrentDB client should automatically register + /// message type names based on the CLR type using naming convention. + /// By default, it's enabled. + /// + public AutomaticTypeMappingRegistration? AutomaticTypeMappingRegistration { get; set; } + /// /// Associates a message type with a specific stream category to enable automatic deserialization. /// In event sourcing, streams are often prefixed with a category (e.g., "user-123", "order-456"). @@ -191,13 +323,13 @@ IMessageTypeNamingStrategy messageTypeNamingStrategy /// /// /// // Register event types that can appear in user streams - /// settings.RegisterMessageTypeForCategory<UserCreated>("user") - /// .RegisterMessageTypeForCategory<UserUpdated>("user") - /// .RegisterMessageTypeForCategory<UserDeleted>("user"); + /// settings.RegisterForCategory<UserRegistered>("user") + /// .RegisterForCategory<RoleAssigned>("user") + /// .RegisterForCategory<UserDeleted>("user"); /// /// - public KurrentDBClientSerializationSettings RegisterMessageTypeForCategory(string categoryName) => - RegisterMessageTypeForCategory(categoryName, typeof(T)); + public MessageTypeMappingSettings RegisterForCategory(string categoryName) => + RegisterForCategory(categoryName, typeof(T)); /// /// Registers multiple message types for a specific stream category. @@ -205,10 +337,11 @@ public KurrentDBClientSerializationSettings RegisterMessageTypeForCategory(st /// The category name to register the types with. /// The message types to register. /// The current instance for method chaining. - public KurrentDBClientSerializationSettings RegisterMessageTypeForCategory(string categoryName, params Type[] types) { - CategoryMessageTypesMap[categoryName] = CategoryMessageTypesMap.TryGetValue(categoryName, out var current) - ? [..current, ..types] - : types; + public MessageTypeMappingSettings RegisterForCategory(string categoryName, params Type[] types) { + CategoryTypesMap[categoryName] = + CategoryTypesMap.TryGetValue(categoryName, out var current) + ? [..current, ..types] + : types; return this; } @@ -228,12 +361,12 @@ public KurrentDBClientSerializationSettings RegisterMessageTypeForCategory(strin /// /// /// // Register me types with their corresponding type identifiers - /// settings.RegisterMessageType<UserCreated>("user-created-v1") - /// .RegisterMessageType<OrderPlaced>("order-placed-v2"); + /// settings.Register<UserRegistered>("user_registered-v1") + /// .Register<OrderPlaced>("order-placed-v2"); /// /// - public KurrentDBClientSerializationSettings RegisterMessageType(string typeName) => - RegisterMessageType(typeof(T), typeName); + public MessageTypeMappingSettings Register(string typeName) => + Register(typeName, typeof(T)); /// /// Registers a message type with a specific type name. @@ -241,8 +374,8 @@ public KurrentDBClientSerializationSettings RegisterMessageType(string typeNa /// The message type to register. /// The type name to register for the message type. /// The current instance for method chaining. - public KurrentDBClientSerializationSettings RegisterMessageType(Type type, string typeName) { - MessageTypeMap[type] = typeName; + public MessageTypeMappingSettings Register(string typeName, Type type) { + TypeMap[typeName] = type; return this; } @@ -252,9 +385,9 @@ public KurrentDBClientSerializationSettings RegisterMessageType(Type type, strin /// /// Dictionary mapping types to their type names. /// The current instance for method chaining. - public KurrentDBClientSerializationSettings RegisterMessageTypes(IDictionary typeMap) { + public MessageTypeMappingSettings Register(IDictionary typeMap) { foreach (var map in typeMap) { - MessageTypeMap[map.Key] = map.Value; + TypeMap[map.Key] = map.Value; } return this; @@ -266,7 +399,7 @@ public KurrentDBClientSerializationSettings RegisterMessageTypes(IDictionary /// The metadata class type containing properties matching the expected metadata fields. /// The current instance for method chaining. - public KurrentDBClientSerializationSettings UseMetadataType() => + public MessageTypeMappingSettings UseMetadataType() => UseMetadataType(typeof(T)); /// @@ -275,86 +408,38 @@ public KurrentDBClientSerializationSettings UseMetadataType() => /// /// The metadata class type containing properties matching the expected metadata fields. /// The current instance for method chaining. - public KurrentDBClientSerializationSettings UseMetadataType(Type type) { + public MessageTypeMappingSettings UseMetadataType(Type type) { DefaultMetadataType = type; return this; } - - /// - /// Creates a deep copy of the current serialization settings. - /// - /// A new instance with copied settings. - internal KurrentDBClientSerializationSettings Clone() { - return new KurrentDBClientSerializationSettings { - BytesSerializer = BytesSerializer, - JsonSerializer = JsonSerializer, - DefaultContentType = DefaultContentType, - MessageTypeMap = new Dictionary(MessageTypeMap), - CategoryMessageTypesMap = new Dictionary(CategoryMessageTypesMap), - MessageTypeNamingStrategy = MessageTypeNamingStrategy - }; - } -} - -/// -/// Provides operation-specific serialization settings that override the global client configuration -/// for individual operations like reading from or appending to streams. This allows fine-tuning -/// serialization behavior on a per-operation basis without changing the client-wide settings. -/// -public class OperationSerializationSettings { - /// - /// Controls whether mes should be automatically deserialized for this specific operation. - /// When enabled (the default), messages will be converted to their appropriate CLR types. - /// When disabled, messages will be returned in their raw serialized form. - /// - public AutomaticDeserialization AutomaticDeserialization { get; private set; } = AutomaticDeserialization.Enabled; - - /// - /// A callback that allows customizing serialization settings for this specific operation. - /// This can be used to override type mappings, serializers, or other settings just for - /// the scope of a single operation without affecting other operations. - /// - public Action? ConfigureSettings { get; private set; } - - /// - /// A pre-configured settings instance that disables automatic deserialization. - /// Use this when you need to access raw message data in its serialized form. - /// - public static readonly OperationSerializationSettings Disabled = new OperationSerializationSettings { - AutomaticDeserialization = AutomaticDeserialization.Disabled - }; - - /// - /// Creates operation-specific serialization settings with custom configuration while keeping - /// automatic deserialization enabled. This allows operation-specific type mappings or - /// serializer settings without changing the global client configuration. - /// - /// A callback to customize serialization settings for this operation. - /// A configured instance of with enabled deserialization. - public static OperationSerializationSettings Configure(Action configure) => - new OperationSerializationSettings { - AutomaticDeserialization = AutomaticDeserialization.Enabled, - ConfigureSettings = configure - }; -} - -/// -/// Controls whether the KurrentDB client should automatically deserialize message payloads -/// into their corresponding CLR types based on the configured type mappings. -/// -public enum AutomaticDeserialization { + /// /// Disables automatic deserialization. Messages will be returned in their raw serialized form, /// requiring manual deserialization by the application. Use this when you need direct access to the raw data /// or when working with messages that don't have registered type mappings. /// - Disabled = 0, + /// The current instance for method chaining. + public MessageTypeMappingSettings DisableAutomaticRegistration() { + AutomaticTypeMappingRegistration = Client.AutomaticTypeMappingRegistration.Disabled; + return this; + } + /// - /// Enables automatic deserialization. The client will attempt to convert messages into their appropriate - /// CLR types using the configured serializers and type mappings. This simplifies working with strongly-typed - /// domain messages but requires proper type registration. + /// Disables automatic type registration. If you use this setting, you need to register all type mappings manually. + /// If the type mapping is not registered for the specific message, the exception will be thrown. /// - Enabled = 1 + /// The current instance for method chaining. + public MessageTypeMappingSettings EnableAutomaticRegistration() { + AutomaticTypeMappingRegistration = Client.AutomaticTypeMappingRegistration.Enabled; + + return this; + } + + internal MessageTypeMappingSettings Clone() => + new MessageTypeMappingSettings { + TypeMap = new Dictionary(TypeMap), + CategoryTypesMap = new Dictionary(CategoryTypesMap), + }; } diff --git a/src/KurrentDB.Client/Core/KurrentDBClientSettings.cs b/src/KurrentDB.Client/Core/KurrentDBClientSettings.cs index 46056a5ef..27f273770 100644 --- a/src/KurrentDB.Client/Core/KurrentDBClientSettings.cs +++ b/src/KurrentDB.Client/Core/KurrentDBClientSettings.cs @@ -62,6 +62,6 @@ public partial class KurrentDBClientSettings { /// Provides configuration options for messages serialization and deserialization in the KurrentDB client. /// If null, default settings are used. /// - public KurrentDBClientSerializationSettings Serialization { get; set; } = KurrentDBClientSerializationSettings.Default(); + public KurrentDBClientSerializationSettings Serialization { get; set; } = KurrentDBClientSerializationSettings.Get(); } } diff --git a/src/KurrentDB.Client/Core/MessageData.cs b/src/KurrentDB.Client/Core/MessageData.cs new file mode 100644 index 000000000..1b7c424d0 --- /dev/null +++ b/src/KurrentDB.Client/Core/MessageData.cs @@ -0,0 +1,137 @@ +using System.Net.Http.Headers; + +namespace KurrentDB.Client { + /// + /// Represents a message to be written. + /// + public sealed class MessageData { + /// + /// The raw bytes of the message data. + /// + public readonly ReadOnlyMemory Data; + + /// + /// The of the message, used as part of the idempotent write check. + /// + public readonly Uuid MessageId; + + /// + /// The raw bytes of the message metadata. + /// + public readonly ReadOnlyMemory Metadata; + + /// + /// The name of the message type. It is strongly recommended that these + /// use lowerCamelCase if projections are to be used. + /// + public readonly string Type; + + /// + /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. + /// + public readonly string ContentType; + + /// + /// Constructs a new . + /// + /// The name of the message type. It is strongly recommended that these use lowerCamelCase if projections are to be used. + /// The raw bytes of the message data. + /// The raw bytes of the message metadata. + /// The of the message, used as part of the idempotent write check. + /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. + /// + /// + /// + /// // Create an message with data and metadata + /// var orderPlaced = new OrderPlaced { OrderId = "ORD-123", Amount = 99.99m }; + /// + /// var metadata = new MessageMetadata { + /// UserId = "user-456", + /// Timestamp = DateTimeOffset.UtcNow, + /// CorrelationId = correlationId + /// }; + /// + /// var type = typeof(OrderPlaced).FullName!; + /// var dataBytes = JsonSerializer.SerializeToUtf8Bytes(orderPlaced); + /// var metadataBytes = JsonSerializer.SerializeToUtf8Bytes(metadata); + /// + /// // Let the system assign an ID automatically + /// var message = new MessageData(type, dataBytes, metadataBytes); + /// + /// // Or specify a custom ID + /// var messageWithId = new MessageData(type, dataBytes, metadataBytes, Uuid.NewUuid()); + /// + /// + public MessageData( + string type, + ReadOnlyMemory data, + ReadOnlyMemory? metadata = null, + Uuid? messageId = null, + string contentType = Constants.Metadata.ContentTypes.ApplicationJson + ) { + messageId ??= Uuid.NewUuid(); + + if (messageId == Uuid.Empty) { + throw new ArgumentOutOfRangeException(nameof(messageId)); + } + + MediaTypeHeaderValue.Parse(contentType); + + if (contentType != Constants.Metadata.ContentTypes.ApplicationJson && + contentType != Constants.Metadata.ContentTypes.ApplicationOctetStream) { + throw new ArgumentOutOfRangeException( + nameof(contentType), + contentType, + $"Only {Constants.Metadata.ContentTypes.ApplicationJson} or {Constants.Metadata.ContentTypes.ApplicationOctetStream} are acceptable values." + ); + } + + MessageId = messageId.Value; + Type = type; + Data = data; + Metadata = metadata ?? Array.Empty(); + ContentType = contentType; + } + + /// + /// Creates a new with the specified message type, id, binary data and metadata + /// + /// The name of the message type. It is strongly recommended that these use lowerCamelCase if projections are to be used. + /// The raw bytes of the message data. + /// Optional metadata providing additional context about the message, such as correlation IDs, timestamps, or user information. + /// Unique identifier for this specific message instance. + /// The Content-Type of the . Valid values are 'application/json' and 'application/octet-stream'. + /// A new immutable instance with the specified properties. + /// Thrown when messageId is explicitly set to Uuid.Empty, which is an invalid identifier. + /// + /// + /// // Create an message with data and metadata + /// var orderPlaced = new OrderPlaced { OrderId = "ORD-123", Amount = 99.99m }; + /// + /// var metadata = new MessageMetadata { + /// UserId = "user-456", + /// Timestamp = DateTimeOffset.UtcNow, + /// CorrelationId = correlationId + /// }; + /// + /// var type = typeof(OrderPlaced).FullName!; + /// var dataBytes = JsonSerializer.SerializeToUtf8Bytes(orderPlaced); + /// var metadataBytes = JsonSerializer.SerializeToUtf8Bytes(metadata); + /// + /// // Let the system assign an ID automatically + /// var message = MessageData.From(type, dataBytes, metadataBytes); + /// + /// // Or specify a custom ID + /// var messageWithId = MessageData.From(type, dataBytes, metadataBytes, Uuid.NewUuid()); + /// + /// + public static MessageData From( + string type, + ReadOnlyMemory data, + ReadOnlyMemory? metadata = null, + Uuid? messageId = null, + string contentType = Constants.Metadata.ContentTypes.ApplicationJson + ) => + new(type, data, metadata, messageId, contentType); + } +} diff --git a/src/KurrentDB.Client/Core/OperationOptions.cs b/src/KurrentDB.Client/Core/OperationOptions.cs new file mode 100644 index 000000000..f3dca9b88 --- /dev/null +++ b/src/KurrentDB.Client/Core/OperationOptions.cs @@ -0,0 +1,28 @@ +namespace KurrentDB.Client; + +/// +/// A class representing the options to apply to an individual operation. +/// +public class OperationOptions { + + /// + /// Maximum time that the operation will be run + /// + public TimeSpan? Deadline { get; set; } + + /// + /// The for the operation. + /// + public UserCredentials? UserCredentials { get; set; } + + /// + /// Clones a copy of the current . + /// + /// + public OperationOptions With(KurrentDBClientSettings clientSettings) { + Deadline ??= clientSettings.DefaultDeadline; + UserCredentials ??= clientSettings.DefaultCredentials; + + return this; + } +} diff --git a/src/KurrentDB.Client/Core/Serialization/Message.cs b/src/KurrentDB.Client/Core/Serialization/Message.cs index de553c556..cda7cdeae 100644 --- a/src/KurrentDB.Client/Core/Serialization/Message.cs +++ b/src/KurrentDB.Client/Core/Serialization/Message.cs @@ -1,6 +1,4 @@ -using KurrentDB.Client; - -namespace KurrentDB.Client.Core.Serialization; +namespace KurrentDB.Client; /// /// Represents a message wrapper in the KurrentDB system, containing both domain data and optional metadata. @@ -9,7 +7,23 @@ namespace KurrentDB.Client.Core.Serialization; /// The message domain data. /// Optional metadata providing additional context about the message, such as correlation IDs, timestamps, or user information. /// Unique identifier for this specific message instance. When null, the system will auto-generate an ID. -public record Message(object Data, object? Metadata, Uuid? MessageId = null) { +public sealed record Message(object Data, object? Metadata, Uuid MessageId) { + /// + /// Creates a new Message with the specified domain data and random ID, but without metadata. + /// This factory method is a convenient shorthand when working with systems that don't require metadata. + /// + /// The message domain data. + /// A new immutable Message instance containing the provided data and ID with null metadata. + /// + /// + /// // Create a message with a specific ID + /// var userRegistered = new UserRegistered { Id = "123", Name = "Alice" }; + /// var message = Message.From(userRegistered); + /// + /// + public static Message From(object data) => + From(data, null); + /// /// Creates a new Message with the specified domain data and message ID, but without metadata. /// This factory method is a convenient shorthand when working with systems that don't require metadata. @@ -20,9 +34,9 @@ public record Message(object Data, object? Metadata, Uuid? MessageId = null) { /// /// /// // Create a message with a specific ID - /// var userCreated = new UserCreated { Id = "123", Name = "Alice" }; + /// var userRegistered = new UserRegistered { Id = "123", Name = "Alice" }; /// var messageId = Uuid.NewUuid(); - /// var message = Message.From(userCreated, messageId); + /// var message = Message.From(userRegistered, messageId); /// /// public static Message From(object data, Uuid messageId) => @@ -53,10 +67,10 @@ public static Message From(object data, Uuid messageId) => /// var messageWithId = Message.From(orderPlaced, metadata, Uuid.NewUuid()); /// /// - public static Message From(object data, object? metadata = null, Uuid? messageId = null) { + public static Message From(object data, object? metadata, Uuid? messageId = null) { if (messageId == Uuid.Empty) throw new ArgumentOutOfRangeException(nameof(messageId), "Message ID cannot be an empty UUID."); - return new Message(data, metadata, messageId); + return new Message(data, metadata, messageId ?? Uuid.NewUuid()); } } diff --git a/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs b/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs index aff4a6dc2..95d845a8b 100644 --- a/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs +++ b/src/KurrentDB.Client/Core/Serialization/MessageSerializer.cs @@ -1,88 +1,63 @@ -using System.Diagnostics.CodeAnalysis; -using KurrentDB.Client; - namespace KurrentDB.Client.Core.Serialization; +using System.Diagnostics.CodeAnalysis; using static ContentTypeExtensions; interface IMessageSerializer { - public EventData Serialize(Message value, MessageSerializationContext context); + public MessageData Serialize(Message value, MessageSerializationContext context); #if NET48 public bool TryDeserialize(EventRecord record, out Message? deserialized); #else public bool TryDeserialize(EventRecord record, [NotNullWhen(true)] out Message? deserialized); #endif -} -record MessageSerializationContext( - string StreamName, - ContentType ContentType -) { - public string CategoryName => - StreamName.Split('-').FirstOrDefault() ?? "no_stream_category"; + public IMessageSerializer With(OperationSerializationSettings? operationSettings); } +record MessageSerializationContext(MessageTypeNamingResolutionContext NamingResolution); + static class MessageSerializerExtensions { - public static EventData[] Serialize( + public static MessageData[] Serialize( this IMessageSerializer serializer, IEnumerable messages, - MessageSerializationContext context - ) { - return messages.Select(m => serializer.Serialize(m, context)).ToArray(); - } - - public static IMessageSerializer With( - this IMessageSerializer defaultMessageSerializer, - KurrentDBClientSerializationSettings defaultSettings, - OperationSerializationSettings? operationSettings - ) { - if (operationSettings == null) - return defaultMessageSerializer; - - if (operationSettings.AutomaticDeserialization == AutomaticDeserialization.Disabled) - return NullMessageSerializer.Instance; - - if (operationSettings.ConfigureSettings == null) - return defaultMessageSerializer; - - var settings = defaultSettings.Clone(); - operationSettings.ConfigureSettings.Invoke(settings); - - return new MessageSerializer(SchemaRegistry.From(settings)); - } + MessageSerializationContext serializationContext + ) => + messages.Select(m => serializer.Serialize(m, serializationContext)).ToArray(); } -class MessageSerializer(SchemaRegistry schemaRegistry) : IMessageSerializer { - readonly ISerializer _jsonSerializer = - schemaRegistry.GetSerializer(ContentType.Json); +class MessageSerializer(SchemaRegistry schemaRegistry, KurrentDBClientSerializationSettings serializationSettings) + : IMessageSerializer { + readonly SystemTextJsonSerializer _metadataSerializer = + new SystemTextJsonSerializer( + new SystemTextJsonSerializationSettings { Options = KurrentDBClient.StreamMetadataJsonSerializerOptions } + ); - readonly IMessageTypeNamingStrategy _messageTypeNamingStrategy = - schemaRegistry.MessageTypeNamingStrategy; + readonly string _contentType = serializationSettings.DefaultContentType.ToMessageContentType(); - public EventData Serialize(Message message, MessageSerializationContext serializationContext) { - var (data, metadata, eventId) = message; + public MessageData Serialize(Message message, MessageSerializationContext serializationContext) { + var (data, metadata, messageId) = message; - var eventType = _messageTypeNamingStrategy + var messageType = schemaRegistry .ResolveTypeName( message.Data.GetType(), - new MessageTypeNamingResolutionContext(serializationContext.CategoryName) + serializationContext.NamingResolution ); var serializedData = schemaRegistry - .GetSerializer(serializationContext.ContentType) + .GetSerializer(serializationSettings.DefaultContentType) .Serialize(data); var serializedMetadata = metadata != null - ? _jsonSerializer.Serialize(metadata) + ? _metadataSerializer.Serialize(metadata) : ReadOnlyMemory.Empty; - return new EventData( - eventId ?? Uuid.NewUuid(), - eventType, + return new MessageData( + messageType, serializedData, serializedMetadata, - serializationContext.ContentType.ToMessageContentType() + messageId, + _contentType ); } @@ -91,49 +66,56 @@ public bool TryDeserialize(EventRecord record, out Message? deserialized) { #else public bool TryDeserialize(EventRecord record, [NotNullWhen(true)] out Message? deserialized) { #endif - if (!TryResolveClrType(record, out var clrType)) { + if (!schemaRegistry.TryResolveClrType(record, out var clrTypeName)) { deserialized = null; return false; } var data = schemaRegistry .GetSerializer(FromMessageContentType(record.ContentType)) - .Deserialize(record.Data, clrType!); + .Deserialize(record.Data, clrTypeName!); if (data == null) { deserialized = null; return false; } - object? metadata = record.Metadata.Length > 0 && TryResolveClrMetadataType(record, out var clrMetadataType) - ? _jsonSerializer.Deserialize(record.Metadata, clrMetadataType!) - : null; + object? metadata = record.Metadata.Length > 0 + && schemaRegistry.TryResolveClrMetadataType(record, out var clrMetadataType) + ? _metadataSerializer.Deserialize(record.Metadata, clrMetadataType!) + : null; deserialized = Message.From(data, metadata, record.EventId); return true; } - public static MessageSerializer From(KurrentDBClientSerializationSettings? settings = null) { - settings ??= KurrentDBClientSerializationSettings.Default(); + public IMessageSerializer With(OperationSerializationSettings? operationSettings) { + if (operationSettings == null) + return this; - return new MessageSerializer(SchemaRegistry.From(settings)); + if (operationSettings.AutomaticDeserialization == AutomaticDeserialization.Disabled) + return NullMessageSerializer.Instance; + + if (operationSettings.ConfigureSettings == null) + return this; + + var settings = serializationSettings.Clone(); + operationSettings.ConfigureSettings.Invoke(settings); + + return new MessageSerializer(SchemaRegistry.From(settings), settings); } - bool TryResolveClrType(EventRecord record, out Type? clrType) => - schemaRegistry - .MessageTypeNamingStrategy - .TryResolveClrType(record.EventType, out clrType); + public static MessageSerializer From(KurrentDBClientSerializationSettings? settings = null) { + settings ??= KurrentDBClientSerializationSettings.Get(); - bool TryResolveClrMetadataType(EventRecord record, out Type? clrMetadataType) => - schemaRegistry - .MessageTypeNamingStrategy - .TryResolveClrMetadataType(record.EventType, out clrMetadataType); + return new MessageSerializer(SchemaRegistry.From(settings), settings); + } } class NullMessageSerializer : IMessageSerializer { public static readonly NullMessageSerializer Instance = new NullMessageSerializer(); - public EventData Serialize(Message value, MessageSerializationContext context) { + public MessageData Serialize(Message value, MessageSerializationContext context) { throw new InvalidOperationException("Cannot serialize, automatic deserialization is disabled"); } @@ -145,4 +127,8 @@ public bool TryDeserialize(EventRecord eventRecord, [NotNullWhen(true)] out Mess deserialized = null; return false; } + + public IMessageSerializer With(OperationSerializationSettings? operationSettings) { + return this; + } } diff --git a/src/KurrentDB.Client/Core/Serialization/MessageTypeRegistry.cs b/src/KurrentDB.Client/Core/Serialization/MessageTypeRegistry.cs index 885bcca1c..cff13b690 100644 --- a/src/KurrentDB.Client/Core/Serialization/MessageTypeRegistry.cs +++ b/src/KurrentDB.Client/Core/Serialization/MessageTypeRegistry.cs @@ -3,7 +3,7 @@ namespace KurrentDB.Client.Core.Serialization; interface IMessageTypeRegistry { - void Register(Type messageType, string messageTypeName); + void Register(string messageTypeName, Type messageType); string? GetTypeName(Type messageType); string GetOrAddTypeName(Type clrType, Func getTypeName); Type? GetClrType(string messageTypeName); @@ -14,7 +14,7 @@ class MessageTypeRegistry : IMessageTypeRegistry { readonly ConcurrentDictionary _typeMap = new(); readonly ConcurrentDictionary _typeNameMap = new(); - public void Register(Type messageType, string messageTypeName) { + public void Register(string messageTypeName, Type messageType) { _typeNameMap.AddOrUpdate(messageType, messageTypeName, (_, _) => messageTypeName); _typeMap.AddOrUpdate(messageTypeName, messageType, (_, type) => type); } @@ -63,9 +63,9 @@ public string GetOrAddTypeName(Type clrType, Func getTypeName) => static class MessageTypeRegistryExtensions { public static void Register(this IMessageTypeRegistry messageTypeRegistry, string messageTypeName) => - messageTypeRegistry.Register(typeof(T), messageTypeName); + messageTypeRegistry.Register(messageTypeName, typeof(T)); - public static void Register(this IMessageTypeRegistry messageTypeRegistry, IDictionary typeMap) { + public static void Register(this IMessageTypeRegistry messageTypeRegistry, IDictionary typeMap) { foreach (var map in typeMap) { messageTypeRegistry.Register(map.Key, map.Value); } diff --git a/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs b/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs index 28a62b796..8dce8e021 100644 --- a/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs +++ b/src/KurrentDB.Client/Core/Serialization/MessageTypeResolutionStrategy.cs @@ -7,94 +7,53 @@ public interface IMessageTypeNamingStrategy { string ResolveTypeName(Type messageType, MessageTypeNamingResolutionContext resolutionContext); #if NET48 - bool TryResolveClrType(string messageTypeName, out Type? type); + bool TryResolveClrTypeName(EventRecord record, out string? clrTypeName); #else - bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type); + bool TryResolveClrTypeName(EventRecord messageTypeName, [NotNullWhen(true)] out string? clrTypeName); #endif - - -#if NET48 - bool TryResolveClrMetadataType(string messageTypeName, out Type? type); -#else - bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type); -#endif -} - -public record MessageTypeNamingResolutionContext(string CategoryName); - -class MessageTypeNamingStrategyWrapper( - IMessageTypeRegistry messageTypeRegistry, - IMessageTypeNamingStrategy messageTypeNamingStrategy -) : IMessageTypeNamingStrategy { - public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionContext resolutionContext) { - return messageTypeRegistry.GetOrAddTypeName( - messageType, - _ => messageTypeNamingStrategy.ResolveTypeName(messageType, resolutionContext) - ); - } - -#if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { -#else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { -#endif - type = messageTypeRegistry.GetOrAddClrType( - messageTypeName, - _ => messageTypeNamingStrategy.TryResolveClrType(messageTypeName, out var resolvedType) - ? resolvedType - : null - ); - - return type != null; - } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + bool TryResolveClrMetadataTypeName(EventRecord record, out string? clrTypeName); #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + bool TryResolveClrMetadataTypeName(EventRecord messageTypeName, [NotNullWhen(true)] out string? clrTypeName); #endif - type = messageTypeRegistry.GetOrAddClrType( - $"{messageTypeName}-metadata", - _ => messageTypeNamingStrategy.TryResolveClrMetadataType(messageTypeName, out var resolvedType) - ? resolvedType - : null - ); +} - return type != null; - } +public record MessageTypeNamingResolutionContext(string CategoryName) { + public static MessageTypeNamingResolutionContext FromStreamName(string streamName) => + new(streamName.Split('-').FirstOrDefault() ?? "no_stream_category"); } public class DefaultMessageTypeNamingStrategy(Type? defaultMetadataType) : IMessageTypeNamingStrategy { readonly Type _defaultMetadataType = defaultMetadataType ?? typeof(TracingMetadata); - + public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionContext resolutionContext) => - $"{resolutionContext.CategoryName}-{messageType.FullName}"; + $"{resolutionContext.CategoryName}-{messageType.FullName}"; #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, out string? clrTypeName) { #else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, [NotNullWhen(true)] out string? clrTypeName) { #endif + var messageTypeName = record.EventType; var categorySeparatorIndex = messageTypeName.IndexOf('-'); if (categorySeparatorIndex == -1 || categorySeparatorIndex == messageTypeName.Length - 1) { - type = null; + clrTypeName = null; return false; } - var clrTypeName = messageTypeName[(categorySeparatorIndex + 1)..]; - - type = TypeProvider.GetTypeByFullName(clrTypeName); + clrTypeName = messageTypeName[(categorySeparatorIndex + 1)..]; - return type != null; + return true; } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, out string? clrTypeName) { #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, [NotNullWhen(true)] out string? clrTypeName) { #endif - type = _defaultMetadataType; - return true; + clrTypeName = _defaultMetadataType.FullName; + return clrTypeName != null; } } diff --git a/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs b/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs index 4b8020d2f..731d076cc 100644 --- a/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs +++ b/src/KurrentDB.Client/Core/Serialization/SchemaRegistry.cs @@ -1,4 +1,4 @@ -using KurrentDB.Client; +using System.Diagnostics.CodeAnalysis; namespace KurrentDB.Client.Core.Serialization; @@ -28,24 +28,84 @@ public static string ToMessageContentType(this ContentType contentType) => class SchemaRegistry( IDictionary serializers, - IMessageTypeNamingStrategy messageTypeNamingStrategy + IMessageTypeNamingStrategy messageTypeNamingStrategy, + IMessageTypeRegistry messageTypeRegistry, + AutomaticTypeMappingRegistration automaticTypeMappingRegistration ) { - public IMessageTypeNamingStrategy MessageTypeNamingStrategy { get; } = messageTypeNamingStrategy; - public ISerializer GetSerializer(ContentType schemaType) => serializers[schemaType]; + public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionContext resolutionContext) => + messageTypeRegistry.GetTypeName(messageType) ?? + messageTypeNamingStrategy.ResolveTypeName(messageType, resolutionContext); + +#if NET48 + public bool TryResolveClrType(EventRecord record, out Type? type) { +#else + public bool TryResolveClrType(EventRecord record, [NotNullWhen(true)] out Type? type) { +#endif + type = messageTypeRegistry.GetClrType(record.EventType); + + if (type != null) + return true; + + if (automaticTypeMappingRegistration == AutomaticTypeMappingRegistration.Disabled) + return false; + + if (!messageTypeNamingStrategy.TryResolveClrTypeName(record, out var clrTypeName) || clrTypeName == null) + return false; + + type = TypeProvider.GetTypeByFullName(clrTypeName); + + if (type == null) + return false; + + messageTypeRegistry.Register(record.EventType, type); + + return true; + } + +#if NET48 + public bool TryResolveClrMetadataType(EventRecord record, out Type? type) { +#else + public bool TryResolveClrMetadataType(EventRecord record, [NotNullWhen(true)] out Type? type) { +#endif + type = messageTypeRegistry.GetClrType($"{record.EventType}-metadata"); + + if (type != null) + return true; + + if (automaticTypeMappingRegistration == AutomaticTypeMappingRegistration.Disabled) + return false; + + if (!messageTypeNamingStrategy.TryResolveClrMetadataTypeName(record, out var clrTypeName) || clrTypeName == null) + return false; + + type = TypeProvider.GetTypeByFullName(clrTypeName); + + if (type == null) + return false; + + messageTypeRegistry.Register($"{record.EventType}-metadata", type); + + return true; + } + public static SchemaRegistry From(KurrentDBClientSerializationSettings settings) { var messageTypeNamingStrategy = - settings.MessageTypeNamingStrategy ?? new DefaultMessageTypeNamingStrategy(settings.DefaultMetadataType); + settings.MessageTypeNamingStrategy + ?? new DefaultMessageTypeNamingStrategy(settings.MessageTypeMapping.DefaultMetadataType); var categoriesTypeMap = ResolveMessageTypeUsingNamingStrategy( - settings.CategoryMessageTypesMap, + settings.MessageTypeMapping, messageTypeNamingStrategy ); + var automaticTypeMappingRegistration = settings.MessageTypeMapping.AutomaticTypeMappingRegistration + ?? AutomaticTypeMappingRegistration.Enabled; + var messageTypeRegistry = new MessageTypeRegistry(); - messageTypeRegistry.Register(settings.MessageTypeMap); + messageTypeRegistry.Register(settings.MessageTypeMapping.TypeMap); messageTypeRegistry.Register(categoriesTypeMap); var serializers = new Dictionary { @@ -60,18 +120,17 @@ public static SchemaRegistry From(KurrentDBClientSerializationSettings settings) return new SchemaRegistry( serializers, - new MessageTypeNamingStrategyWrapper( - messageTypeRegistry, - settings.MessageTypeNamingStrategy ?? new DefaultMessageTypeNamingStrategy(settings.DefaultMetadataType) - ) + messageTypeNamingStrategy, + messageTypeRegistry, + automaticTypeMappingRegistration ); } - static Dictionary ResolveMessageTypeUsingNamingStrategy( - IDictionary categoryMessageTypesMap, + static Dictionary ResolveMessageTypeUsingNamingStrategy( + MessageTypeMappingSettings messageTypeMappingSettings, IMessageTypeNamingStrategy messageTypeNamingStrategy ) => - categoryMessageTypesMap + messageTypeMappingSettings.CategoryTypesMap .SelectMany( categoryTypes => categoryTypes.Value.Select( type => @@ -85,7 +144,7 @@ IMessageTypeNamingStrategy messageTypeNamingStrategy ) ) .ToDictionary( - ks => ks.Type, - vs => vs.TypeName + ks => ks.TypeName, + vs => vs.Type ); } diff --git a/src/KurrentDB.Client/PersistentSubscriptions/KurrentDBPersistentSubscriptionsClient.Read.cs b/src/KurrentDB.Client/PersistentSubscriptions/KurrentDBPersistentSubscriptionsClient.Read.cs index 790a66caf..45d1b04dd 100644 --- a/src/KurrentDB.Client/PersistentSubscriptions/KurrentDBPersistentSubscriptionsClient.Read.cs +++ b/src/KurrentDB.Client/PersistentSubscriptions/KurrentDBPersistentSubscriptionsClient.Read.cs @@ -6,99 +6,11 @@ using KurrentDB.Client.Core.Serialization; using static EventStore.Client.PersistentSubscriptions.PersistentSubscriptions; using static EventStore.Client.PersistentSubscriptions.ReadResp.ContentOneofCase; +using static KurrentDB.Client.KurrentDBPersistentSubscriptionsClient; namespace KurrentDB.Client { - public class SubscribeToPersistentSubscriptionOptions { - /// - /// The size of the buffer. - /// - public int BufferSize { get; set; } = 10; - - /// - /// The optional user credentials to perform operation with. - /// - public UserCredentials? UserCredentials { get; set; } = null; - - /// - /// Allows to customize or disable the automatic deserialization - /// - public OperationSerializationSettings? SerializationSettings { get; set; } - } - - public class PersistentSubscriptionListener { - /// - /// A handler called when a new event is received over the subscription. - /// -#if NET48 - public Func EventAppeared { get; set; } = - null!; -#else - public required Func EventAppeared { - get; - set; - } -#endif - /// - /// A handler called if the subscription is dropped. - /// - public Action? SubscriptionDropped { get; set; } - - /// - /// Returns the subscription listener with configured handlers - /// - /// Handler invoked when a new event is received over the subscription. - /// A handler invoked if the subscription is dropped. - /// A handler called when a checkpoint is reached. - /// Set the checkpointInterval in subscription filter options to define how often this method is called. - /// - /// - public static PersistentSubscriptionListener Handle( - Func eventAppeared, - Action? subscriptionDropped = null - ) => - new PersistentSubscriptionListener { - EventAppeared = eventAppeared, - SubscriptionDropped = subscriptionDropped - }; - } - partial class KurrentDBPersistentSubscriptionsClient { - /// - /// Subscribes to a persistent subscription. - /// - /// - /// - /// - [Obsolete("SubscribeAsync is no longer supported. Use SubscribeToStream with manual acks instead.", false)] - public async Task SubscribeAsync( - string streamName, - string groupName, - Func eventAppeared, - Action? subscriptionDropped = null, - UserCredentials? userCredentials = null, - int bufferSize = 10, - bool autoAck = true, - CancellationToken cancellationToken = default - ) { - if (autoAck) { - throw new InvalidOperationException( - $"AutoAck is no longer supported. Please use {nameof(SubscribeToStream)} with manual acks instead." - ); - } - - return await SubscribeToStreamAsync( - streamName, - groupName, - PersistentSubscriptionListener.Handle(eventAppeared, subscriptionDropped), - new SubscribeToPersistentSubscriptionOptions { - UserCredentials = userCredentials, - BufferSize = bufferSize, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - } - + internal const int DefaultBufferSize = 10; /// /// Subscribes to a persistent subscription. Messages must be manually acknowledged /// @@ -106,104 +18,40 @@ public async Task SubscribeAsync( /// /// public Task SubscribeToStreamAsync( - string streamName, - string groupName, - Func eventAppeared, - Action? subscriptionDropped = null, - UserCredentials? userCredentials = null, - int bufferSize = 10, - CancellationToken cancellationToken = default - ) => - SubscribeToStreamAsync( - streamName, - groupName, - PersistentSubscriptionListener.Handle(eventAppeared, subscriptionDropped), - new SubscribeToPersistentSubscriptionOptions { - UserCredentials = userCredentials, - BufferSize = bufferSize, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - - /// - /// Subscribes to a persistent subscription. Messages must be manually acknowledged - /// - /// - /// - /// - public async Task SubscribeToStreamAsync( string streamName, string groupName, PersistentSubscriptionListener listener, - SubscribeToPersistentSubscriptionOptions options, + SubscribeToPersistentSubscriptionOptions? options = null, CancellationToken cancellationToken = default - ) { - return await PersistentSubscription + ) => + PersistentSubscription .Confirm( SubscribeToStream(streamName, groupName, options, cancellationToken), listener, _log, cancellationToken - ) - .ConfigureAwait(false); - } - - /// - /// Subscribes to a persistent subscription. Messages must be manually acknowledged. - /// - /// The name of the stream to read events from. - /// The name of the persistent subscription group. - /// The size of the buffer. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public PersistentSubscriptionResult SubscribeToStream( - string streamName, - string groupName, - int bufferSize, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) { - return SubscribeToStream( - streamName, - groupName, - new SubscribeToPersistentSubscriptionOptions { - BufferSize = bufferSize, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - } - + ); - /// - /// Subscribes to a persistent subscription. Messages must be manually acknowledged. + /// Subscribes to a persistent subscription. Messages must be manually acknowledged /// - /// The name of the stream to read events from. - /// The name of the persistent subscription group. - /// The size of the buffer. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public PersistentSubscriptionResult SubscribeToStream( + /// + /// + /// + public Task SubscribeToStreamAsync( string streamName, string groupName, - UserCredentials? userCredentials, + Func eventAppeared, + SubscribeToPersistentSubscriptionOptions? options = null, CancellationToken cancellationToken = default - ) { - return SubscribeToStream( + ) => + SubscribeToStreamAsync( streamName, groupName, - new SubscribeToPersistentSubscriptionOptions { - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, + PersistentSubscriptionListener.Handle(eventAppeared), + options, cancellationToken ); - } /// /// Subscribes to a persistent subscription. Messages must be manually acknowledged. @@ -216,7 +64,7 @@ public PersistentSubscriptionResult SubscribeToStream( public PersistentSubscriptionResult SubscribeToStream( string streamName, string groupName, - SubscribeToPersistentSubscriptionOptions options, + SubscribeToPersistentSubscriptionOptions? options = null, CancellationToken cancellationToken = default ) { if (streamName == null) { @@ -235,12 +83,14 @@ public PersistentSubscriptionResult SubscribeToStream( throw new ArgumentException($"{nameof(groupName)} may not be empty.", nameof(groupName)); } + options ??= new SubscribeToPersistentSubscriptionOptions(); + if (options.BufferSize <= 0) { throw new ArgumentOutOfRangeException(nameof(options.BufferSize)); } var readOptions = new ReadReq.Types.Options { - BufferSize = options.BufferSize, + BufferSize = options.BufferSize ?? DefaultBufferSize, GroupName = groupName, UuidOption = new ReadReq.Types.Options.Types.UUIDOption { Structured = new Empty() } }; @@ -269,40 +119,18 @@ public PersistentSubscriptionResult SubscribeToStream( new() { Options = readOptions }, Settings, options.UserCredentials, - _messageSerializer.With(Settings.Serialization, options.SerializationSettings), + _messageSerializer.With(options.SerializationSettings), cancellationToken ); } - /// - /// Subscribes to a persistent subscription to $all. Messages must be manually acknowledged - /// - public Task SubscribeToAllAsync( - string groupName, - Func eventAppeared, - Action? subscriptionDropped = null, - UserCredentials? userCredentials = null, - int bufferSize = 10, - CancellationToken cancellationToken = default - ) => - SubscribeToAllAsync( - groupName, - PersistentSubscriptionListener.Handle(eventAppeared, subscriptionDropped), - new SubscribeToPersistentSubscriptionOptions { - BufferSize = bufferSize, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - /// /// Subscribes to a persistent subscription to $all. Messages must be manually acknowledged /// public Task SubscribeToAllAsync( string groupName, PersistentSubscriptionListener listener, - SubscribeToPersistentSubscriptionOptions options, + SubscribeToPersistentSubscriptionOptions? options = null, CancellationToken cancellationToken = default ) => SubscribeToStreamAsync( @@ -312,53 +140,17 @@ public Task SubscribeToAllAsync( options, cancellationToken ); - - /// - /// Subscribes to a persistent subscription to $all. Messages must be manually acknowledged. - /// - /// The name of the persistent subscription group. - /// The size of the buffer. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public PersistentSubscriptionResult SubscribeToAll( - string groupName, - int bufferSize, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) => - SubscribeToStream( - SystemStreams.AllStream, - groupName, - new SubscribeToPersistentSubscriptionOptions { - BufferSize = bufferSize, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - - /// - /// Subscribes to a persistent subscription to $all. Messages must be manually acknowledged. - /// - /// The name of the persistent subscription group. - /// The size of the buffer. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public PersistentSubscriptionResult SubscribeToAll( + public Task SubscribeToAllAsync( string groupName, - UserCredentials? userCredentials, + Func eventAppeared, + SubscribeToPersistentSubscriptionOptions? options = null, CancellationToken cancellationToken = default ) => - SubscribeToStream( - SystemStreams.AllStream, + SubscribeToAllAsync( groupName, - new SubscribeToPersistentSubscriptionOptions { - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, + PersistentSubscriptionListener.Handle(eventAppeared), + options, cancellationToken ); @@ -371,7 +163,7 @@ public PersistentSubscriptionResult SubscribeToAll( /// public PersistentSubscriptionResult SubscribeToAll( string groupName, - SubscribeToPersistentSubscriptionOptions options, + SubscribeToPersistentSubscriptionOptions? options = null, CancellationToken cancellationToken = default ) => SubscribeToStream( @@ -703,81 +495,279 @@ public async IAsyncEnumerator GetAsyncEnumerator( } } } + + public class SubscribeToPersistentSubscriptionOptions { + /// + /// The size of the buffer. + /// + public int? BufferSize { get; set; } + + /// + /// The optional user credentials to perform operation with. + /// + public UserCredentials? UserCredentials { get; set; } + + /// + /// Allows to customize or disable the automatic deserialization + /// + public OperationSerializationSettings? SerializationSettings { get; set; } + } + + public class PersistentSubscriptionListener { + /// + /// A handler called when a new event is received over the subscription. + /// +#if NET48 + public Func EventAppeared { get; set; } = + null!; +#else + public required Func EventAppeared { + get; + set; + } +#endif + /// + /// A handler called if the subscription is dropped. + /// + public Action? SubscriptionDropped { get; set; } + + /// + /// Returns the subscription listener with configured handlers + /// + /// Handler invoked when a new event is received over the subscription. + /// A handler invoked if the subscription is dropped. + /// A handler called when a checkpoint is reached. + /// Set the checkpointInterval in subscription filter options to define how often this method is called. + /// + /// + public static PersistentSubscriptionListener Handle( + Func eventAppeared, + Action? subscriptionDropped = null + ) => + new PersistentSubscriptionListener { + EventAppeared = eventAppeared, + SubscriptionDropped = subscriptionDropped + }; + } + + public static class ObsoleteKurrentDBClientPersistentSubscriptionsExtensions { + /// + /// Subscribes to a persistent subscription. + /// + /// + /// + /// + [Obsolete("SubscribeAsync is no longer supported. Use SubscribeToStream with manual acks instead.", false)] + public static async Task SubscribeAsync( + this KurrentDBPersistentSubscriptionsClient dbClient, + string streamName, + string groupName, + Func eventAppeared, + Action? subscriptionDropped = null, + UserCredentials? userCredentials = null, + int bufferSize = DefaultBufferSize, + bool autoAck = true, + CancellationToken cancellationToken = default + ) { + if (autoAck) { + throw new InvalidOperationException( + $"AutoAck is no longer supported. Please use {nameof(SubscribeToStream)} with manual acks instead." + ); + } + + return await dbClient.SubscribeToStreamAsync( + streamName, + groupName, + PersistentSubscriptionListener.Handle(eventAppeared, subscriptionDropped), + new SubscribeToPersistentSubscriptionOptions { + UserCredentials = userCredentials, + BufferSize = bufferSize, + SerializationSettings = OperationSerializationSettings.Disabled + }, + cancellationToken + ); + } - public static class KurrentDBClientPersistentSubscriptionsExtensions { /// /// Subscribes to a persistent subscription. Messages must be manually acknowledged /// /// /// /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToPersistentSubscriptionOptions and get auto-serialization capabilities", + false + )] public static Task SubscribeToStreamAsync( - this KurrentDBPersistentSubscriptionsClient kurrentDbClient, + this KurrentDBPersistentSubscriptionsClient dbClient, string streamName, string groupName, - PersistentSubscriptionListener listener, + Func eventAppeared, + Action? subscriptionDropped = null, + UserCredentials? userCredentials = null, + int bufferSize = DefaultBufferSize, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToStreamAsync( + dbClient.SubscribeToStreamAsync( streamName, groupName, - listener, - new SubscribeToPersistentSubscriptionOptions(), + PersistentSubscriptionListener.Handle(eventAppeared, subscriptionDropped), + new SubscribeToPersistentSubscriptionOptions { + UserCredentials = userCredentials, + BufferSize = bufferSize, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); /// /// Subscribes to a persistent subscription. Messages must be manually acknowledged. /// - /// + /// /// The name of the stream to read events from. /// The name of the persistent subscription group. + /// The size of the buffer. + /// The optional user credentials to perform operation with. /// The optional . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToPersistentSubscriptionOptions and get auto-serialization capabilities", + false + )] public static KurrentDBPersistentSubscriptionsClient.PersistentSubscriptionResult SubscribeToStream( - this KurrentDBPersistentSubscriptionsClient kurrentDbClient, + this KurrentDBPersistentSubscriptionsClient dbClient, string streamName, string groupName, + int bufferSize, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToStream( + dbClient.SubscribeToStream( streamName, groupName, - new SubscribeToPersistentSubscriptionOptions(), + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferSize, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, + cancellationToken + ); + + /// + /// Subscribes to a persistent subscription. Messages must be manually acknowledged. + /// + /// + /// The name of the stream to read events from. + /// The name of the persistent subscription group. + /// The size of the buffer. + /// The optional user credentials to perform operation with. + /// The optional . + /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToPersistentSubscriptionOptions and get auto-serialization capabilities", + false + )] + public static KurrentDBPersistentSubscriptionsClient.PersistentSubscriptionResult SubscribeToStream( + this KurrentDBPersistentSubscriptionsClient dbClient, + string streamName, + string groupName, + UserCredentials? userCredentials, + CancellationToken cancellationToken = default + ) => + dbClient.SubscribeToStream( + streamName, + groupName, + new SubscribeToPersistentSubscriptionOptions { + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); /// /// Subscribes to a persistent subscription to $all. Messages must be manually acknowledged /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToPersistentSubscriptionOptions and get auto-serialization capabilities", + false + )] public static Task SubscribeToAllAsync( - this KurrentDBPersistentSubscriptionsClient kurrentDbClient, + this KurrentDBPersistentSubscriptionsClient dbClient, string groupName, - PersistentSubscriptionListener listener, + Func eventAppeared, + Action? subscriptionDropped = null, + UserCredentials? userCredentials = null, + int bufferSize = DefaultBufferSize, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToAllAsync( + dbClient.SubscribeToAllAsync( groupName, - listener, - new SubscribeToPersistentSubscriptionOptions(), + PersistentSubscriptionListener.Handle(eventAppeared, subscriptionDropped), + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferSize, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, + cancellationToken + ); + + /// + /// Subscribes to a persistent subscription to $all. Messages must be manually acknowledged. + /// + /// + /// The name of the persistent subscription group. + /// The size of the buffer. + /// The optional user credentials to perform operation with. + /// The optional . + /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToPersistentSubscriptionOptions and get auto-serialization capabilities", + false + )] + public static KurrentDBPersistentSubscriptionsClient.PersistentSubscriptionResult SubscribeToAll( + this KurrentDBPersistentSubscriptionsClient dbClient, + string groupName, + int bufferSize, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) => + dbClient.SubscribeToStream( + SystemStreams.AllStream, + groupName, + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferSize, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); /// /// Subscribes to a persistent subscription to $all. Messages must be manually acknowledged. /// - /// + /// /// The name of the persistent subscription group. + /// The size of the buffer. + /// The optional user credentials to perform operation with. /// The optional . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToPersistentSubscriptionOptions and get auto-serialization capabilities", + false + )] public static KurrentDBPersistentSubscriptionsClient.PersistentSubscriptionResult SubscribeToAll( - this KurrentDBPersistentSubscriptionsClient kurrentDbClient, + this KurrentDBPersistentSubscriptionsClient dbClient, string groupName, + UserCredentials? userCredentials, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToAll( + dbClient.SubscribeToStream( + SystemStreams.AllStream, groupName, - new SubscribeToPersistentSubscriptionOptions(), + new SubscribeToPersistentSubscriptionOptions { + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs index 0408dbc04..343b859b6 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Append.cs @@ -12,6 +12,7 @@ using Kurrent.Diagnostics.Tracing; using static EventStore.Client.Streams.AppendResp.Types.WrongExpectedVersion; using static EventStore.Client.Streams.Streams; +using static KurrentDB.Client.Core.Serialization.MessageTypeNamingResolutionContext; namespace KurrentDB.Client { public partial class KurrentDBClient { @@ -19,62 +20,52 @@ public partial class KurrentDBClient { /// Appends events asynchronously to a stream. Messages are serialized using default or custom serialization configured through /// /// The name of the stream to append events to. + /// The expected of the stream to append to. /// Messages to append to the stream. - /// Optional settings for the append operation, e.g. expected stream position for optimistic concurrency check + /// Optional settings for the append operation, e.g. deadline, user credentials etc. /// The optional . /// public Task AppendToStreamAsync( string streamName, + StreamState expectedState, IEnumerable messages, - AppendToStreamOptions options, + AppendToStreamOptions? options = null, CancellationToken cancellationToken = default ) { - var serializationContext = new MessageSerializationContext( - streamName, - Settings.Serialization.DefaultContentType - ); + var messageSerializationContext = new MessageSerializationContext(FromStreamName(streamName)); - var eventsData = _messageSerializer.Serialize(messages, serializationContext); + var messageData = _messageSerializer.With(options?.SerializationSettings) + .Serialize(messages, messageSerializationContext); - return AppendToStreamAsync( - streamName, - options.ExpectedStreamState ?? StreamState.Any, - eventsData, - options.ConfigureOperationOptions, - options.Deadline, - options.UserCredentials, - cancellationToken - ); + return AppendToStreamAsync(streamName, expectedState, messageData, options, cancellationToken); } /// - /// Appends events asynchronously to a stream. + /// Appends events asynchronously to a stream using raw message data. + /// If you want to use auto-serialization, use overload with .. + /// This method intends to cover low-level scenarios in which you want to have full control of the serialization mechanism. /// /// The name of the stream to append events to. - /// The expected of the stream to append to. - /// An to append to the stream. - /// An to configure the operation's options. - /// - /// The for the operation. + /// The expected of the stream to append to. + /// Raw message data to append to the stream. + /// Optional settings for the append operation, e.g. deadline, user credentials etc. /// The optional . /// public async Task AppendToStreamAsync( string streamName, StreamState expectedState, - IEnumerable eventData, - Action? configureOperationOptions = null, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, + IEnumerable messageData, + AppendToStreamOptions? options = null, CancellationToken cancellationToken = default ) { - var operationOptions = Settings.OperationOptions.Clone(); - configureOperationOptions?.Invoke(operationOptions); + options ??= new AppendToStreamOptions(); + options.With(Settings.OperationOptions); _log.LogDebug("Append to stream - {streamName}@{expectedState}.", streamName, expectedState); var task = - userCredentials == null && await BatchAppender.IsUsable().ConfigureAwait(false) - ? BatchAppender.Append(streamName, expectedState, eventData, deadline, cancellationToken) + options.UserCredentials == null && await BatchAppender.IsUsable().ConfigureAwait(false) + ? BatchAppender.Append(streamName, expectedState, messageData, options.Deadline, cancellationToken) : AppendToStreamInternal( await GetChannelInfo(cancellationToken).ConfigureAwait(false), new AppendReq { @@ -82,25 +73,24 @@ await GetChannelInfo(cancellationToken).ConfigureAwait(false), StreamIdentifier = streamName } }.WithAnyStreamRevision(expectedState), - eventData, - operationOptions, - deadline, - userCredentials, + messageData, + options, cancellationToken ); - return (await task.ConfigureAwait(false)).OptionallyThrowWrongExpectedVersionException(operationOptions); + return (await task.ConfigureAwait(false)).OptionallyThrowWrongExpectedVersionException(options); } ValueTask AppendToStreamInternal( ChannelInfo channelInfo, AppendReq header, - IEnumerable eventData, - KurrentDBClientOperationOptions operationOptions, - TimeSpan? deadline, - UserCredentials? userCredentials, + IEnumerable messageData, + AppendToStreamOptions operationOptions, CancellationToken cancellationToken ) { + var userCredentials = operationOptions.UserCredentials; + var deadline = operationOptions.Deadline; + var tags = new ActivityTagsCollection() .WithRequiredTag( TelemetryTags.Kurrent.Stream, @@ -129,10 +119,10 @@ await call.RequestStream .WriteAsync(header) .ConfigureAwait(false); - foreach (var e in eventData) { + foreach (var e in messageData) { var appendReq = new AppendReq { ProposedMessage = new() { - Id = e.EventId.ToDto(), + Id = e.MessageId.ToDto(), Data = ByteString.CopyFrom(e.Data.Span), CustomMetadata = ByteString.CopyFrom(e.Metadata.InjectTracingContext(Activity.Current)), Metadata = { @@ -181,7 +171,9 @@ IWriteResult HandleSuccessAppend(AppendResp response, AppendReq header) { } IWriteResult HandleWrongExpectedRevision( - AppendResp response, AppendReq header, KurrentDBClientOperationOptions operationOptions + AppendResp response, + AppendReq header, + AppendToStreamOptions operationOptions ) { var actualStreamRevision = response.WrongExpectedVersion.CurrentRevisionOptionCase == CurrentRevisionOptionOneofCase.CurrentRevision @@ -195,7 +187,7 @@ IWriteResult HandleWrongExpectedRevision( actualStreamRevision ); - if (operationOptions.ThrowOnAppendFailure) { + if (operationOptions.ThrowOnAppendFailure == true) { if (response.WrongExpectedVersion.ExpectedRevisionOptionCase == ExpectedRevisionOptionOneofCase.ExpectedRevision) { throw new WrongExpectedVersionException( @@ -232,7 +224,7 @@ IWriteResult HandleWrongExpectedRevision( } class StreamAppender : IDisposable { - readonly KurrentDBClientSettings _settings; + readonly KurrentDBClientSettings _settings; readonly CancellationToken _cancellationToken; readonly Action _onException; readonly Channel _channel; @@ -259,8 +251,10 @@ Action onException } public ValueTask Append( - string streamName, StreamState expectedStreamState, - IEnumerable events, TimeSpan? timeoutAfter, + string streamName, + StreamState expectedStreamState, + IEnumerable events, + TimeSpan? timeoutAfter, CancellationToken cancellationToken = default ) => AppendInternal( @@ -273,7 +267,7 @@ public ValueTask Append( ValueTask AppendInternal( BatchAppendReq.Types.Options options, - IEnumerable events, + IEnumerable events, CancellationToken cancellationToken ) { var tags = new ActivityTagsCollection() @@ -376,7 +370,9 @@ out var writeResult } IEnumerable GetRequests( - IEnumerable events, BatchAppendReq.Types.Options options, Uuid correlationId + IEnumerable events, + BatchAppendReq.Types.Options options, + Uuid correlationId ) { var batchSize = 0; var first = true; @@ -387,7 +383,7 @@ IEnumerable GetRequests( var proposedMessage = new BatchAppendReq.Types.ProposedMessage { Data = ByteString.CopyFrom(eventData.Data.Span), CustomMetadata = ByteString.CopyFrom(eventData.Metadata.InjectTracingContext(Activity.Current)), - Id = eventData.EventId.ToDto(), + Id = eventData.MessageId.ToDto(), Metadata = { { Constants.Metadata.Type, eventData.Type }, { Constants.Metadata.ContentType, eventData.ContentType } @@ -431,88 +427,89 @@ public static class KurrentDBClientAppendToStreamExtensions { /// /// /// The name of the stream to append events to. + /// The expected of the stream to append to. /// Messages to append to the stream. + /// Optional settings for the append operation, e.g. expected stream position for optimistic concurrency check /// The optional . /// public static Task AppendToStreamAsync( this KurrentDBClient dbClient, string streamName, - IEnumerable messages, - CancellationToken cancellationToken = default - ) - => dbClient.AppendToStreamAsync( - streamName, - messages, - new AppendToStreamOptions(), - cancellationToken - ); - - /// - /// Appends events asynchronously to a stream. Messages are serialized using default or custom serialization configured through - /// - /// - /// The name of the stream to append events to. - /// Messages to append to the stream. - /// The optional . - /// - public static Task AppendToStreamAsync( - this KurrentDBClient dbClient, - string streamName, + StreamState expectedState, IEnumerable messages, + AppendToStreamOptions? options = null, CancellationToken cancellationToken = default ) => dbClient.AppendToStreamAsync( streamName, - messages.Select(m => Message.From(m)), - new AppendToStreamOptions(), + expectedState, + messages.Select(Message.From), + options, cancellationToken ); - + /// - /// Appends events asynchronously to a stream. Messages are serialized using default or custom serialization configured through + /// Appends events asynchronously to a stream. /// /// - /// The name of the stream to append events to. - /// Messages to append to the stream. - /// Optional settings for the append operation, e.g. expected stream position for optimistic concurrency check + /// The name of the stream to append events to. + /// The expected of the stream to append to. + /// An to append to the stream. + /// An to configure the operation's options. + /// + /// The for the operation. /// The optional . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with Message for auto-serialization or method with options parameter for raw serialization", + false + )] public static Task AppendToStreamAsync( this KurrentDBClient dbClient, string streamName, - IEnumerable messages, - AppendToStreamOptions options, + StreamState expectedState, + IEnumerable eventData, + Action? configureOperationOptions = null, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default - ) - => dbClient.AppendToStreamAsync( + ) { + var operationOptions = new AppendToStreamOptions { + Deadline = deadline, + UserCredentials = userCredentials, + }; + + configureOperationOptions?.Invoke(operationOptions); + + return dbClient.AppendToStreamAsync( streamName, - messages.Select(m => Message.From(m)), - options, + expectedState, + eventData.Select(e => (MessageData)e), + operationOptions, cancellationToken ); + } } - // TODO: In the follow up PR merge StreamState and StreamRevision into a one thing - public class AppendToStreamOptions { + public class AppendToStreamOptions : OperationOptions { /// - /// The expected of the stream to append to. + /// Whether or not to immediately throw a when an append fails. /// - public StreamState? ExpectedStreamState { get; set; } + public bool? ThrowOnAppendFailure { get; set; } /// - /// An to configure the operation's options. + /// The batch size, in bytes. /// - public Action? ConfigureOperationOptions { get; set; } + public int? BatchAppendSize { get; set; } /// - /// Maximum time that the operation will be run + /// Clones a copy of the current . /// - public TimeSpan? Deadline { get; set; } - - /// - /// The for the operation. - /// - public UserCredentials? UserCredentials { get; set; } + /// + public void With(KurrentDBClientOperationOptions clientOperationOptions) { + ThrowOnAppendFailure ??= clientOperationOptions.ThrowOnAppendFailure; + BatchAppendSize ??= clientOperationOptions.BatchAppendSize; + } /// /// Allows to customize or disable the automatic deserialization diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Delete.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Delete.cs index 17a03f721..fbf3a0afd 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Delete.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Delete.cs @@ -8,33 +8,79 @@ public partial class KurrentDBClient { /// /// The name of the stream to delete. /// The expected of the stream being deleted. - /// The maximum time to wait before terminating the call. - /// The optional to perform operation with. + /// Optional settings for the delete operation, e.g. deadline, user credentials etc. /// The optional . /// public Task DeleteAsync( string streamName, StreamState expectedState, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => DeleteInternal(new DeleteReq { - Options = new DeleteReq.Types.Options { - StreamIdentifier = streamName - } - }.WithAnyStreamRevision(expectedState), deadline, userCredentials, cancellationToken); + DeleteOptions? options = null, + CancellationToken cancellationToken = default + ) => + DeleteInternal( + new DeleteReq { + Options = new DeleteReq.Types.Options { + StreamIdentifier = streamName + } + }.WithAnyStreamRevision(expectedState), + options, + cancellationToken + ); - private async Task DeleteInternal(DeleteReq request, - TimeSpan? deadline, - UserCredentials? userCredentials, - CancellationToken cancellationToken) { + async Task DeleteInternal( + DeleteReq request, + DeleteOptions? options, + CancellationToken cancellationToken + ) { _log.LogDebug("Deleting stream {streamName}.", request.Options.StreamIdentifier); var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new EventStore.Client.Streams.Streams.StreamsClient( - channelInfo.CallInvoker).DeleteAsync(request, - KurrentDBCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); + using var call = new Streams.StreamsClient(channelInfo.CallInvoker).DeleteAsync( + request, + KurrentDBCallOptions.CreateNonStreaming( + Settings, + options?.Deadline, + options?.UserCredentials, + cancellationToken + ) + ); + var result = await call.ResponseAsync.ConfigureAwait(false); return new DeleteResult(new Position(result.Position.CommitPosition, result.Position.PreparePosition)); } } + + public class DeleteOptions : OperationOptions; + + [Obsolete("Those extensions may be removed in the future versions", false)] + public static class ObsoleteKurrentDBClientDeleteExtensions { + /// + /// Deletes a stream asynchronously. + /// + /// + /// The name of the stream to delete. + /// The expected of the stream being deleted. + /// The maximum time to wait before terminating the call. + /// The optional to perform operation with. + /// The optional . + /// + [Obsolete( + "This method may be removed in future releases. Use the overload with DeleteOptions parameter", + false + )] + public static Task DeleteAsync( + this KurrentDBClient dbClient, + string streamName, + StreamState expectedState, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) => + dbClient.DeleteAsync( + streamName, + expectedState, + new DeleteOptions{ Deadline = deadline, UserCredentials = userCredentials }, + cancellationToken + ); + } } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs index 629d12ced..4b3b1b2cf 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Metadata.cs @@ -8,29 +8,48 @@ public partial class KurrentDBClient { /// Asynchronously reads the metadata for a stream /// /// The name of the stream to read the metadata for. - /// - /// The optional to perform operation with. + /// /// The optional . /// - public async Task GetStreamMetadataAsync(string streamName, TimeSpan? deadline = null, - UserCredentials? userCredentials = null, CancellationToken cancellationToken = default) { + public async Task GetStreamMetadataAsync( + string streamName, + OperationOptions? operationOptions = null, + CancellationToken cancellationToken = default + ) { _log.LogDebug("Read stream metadata for {streamName}.", streamName); try { - var result = ReadStreamAsync(Direction.Backwards, SystemStreams.MetastreamOf(streamName), - StreamPosition.End, 1, false, deadline, userCredentials, cancellationToken); - await foreach (var message in result.Messages.ConfigureAwait(false)) { + var result = ReadStreamAsync( + SystemStreams.MetastreamOf(streamName), + new ReadStreamOptions { + Direction = Direction.Backwards, + StreamPosition = StreamPosition.End, + ResolveLinkTos = false, + MaxCount = 1, + Deadline = operationOptions?.Deadline, + UserCredentials = operationOptions?.UserCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, + cancellationToken + ); + + await foreach (var message in + result.Messages.ConfigureAwait(false).WithCancellation(cancellationToken)) { if (message is not StreamMessage.Event(var resolvedEvent)) { continue; } - return StreamMetadataResult.Create(streamName, resolvedEvent.OriginalEventNumber, - JsonSerializer.Deserialize(resolvedEvent.Event.Data.Span, - StreamMetadataJsonSerializerOptions)); + return StreamMetadataResult.Create( + streamName, + resolvedEvent.OriginalEventNumber, + JsonSerializer.Deserialize( + resolvedEvent.Event.Data.Span, + StreamMetadataJsonSerializerOptions + ) + ); } + } catch (StreamNotFoundException) { } - } catch (StreamNotFoundException) { - } _log.LogWarning("Stream metadata for {streamName} not found.", streamName); return StreamMetadataResult.None(streamName); } @@ -41,37 +60,113 @@ public async Task GetStreamMetadataAsync(string streamName /// The name of the stream to set metadata for. /// The of the stream to append to. /// A representing the new metadata. - /// An to configure the operation's options. - /// - /// The optional to perform operation with. + /// /// The optional . /// - public Task SetStreamMetadataAsync(string streamName, StreamState expectedState, - StreamMetadata metadata, Action? configureOperationOptions = null, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { - var options = Settings.OperationOptions.Clone(); - configureOperationOptions?.Invoke(options); + public Task SetStreamMetadataAsync( + string streamName, + StreamState expectedState, + StreamMetadata metadata, + SetStreamMetadataOptions? operationOptions = null, + CancellationToken cancellationToken = default + ) { + operationOptions ??= new SetStreamMetadataOptions(); + operationOptions.With(Settings.OperationOptions); - return SetStreamMetadataInternal(metadata, new AppendReq { - Options = new AppendReq.Types.Options { - StreamIdentifier = SystemStreams.MetastreamOf(streamName) - } - }.WithAnyStreamRevision(expectedState), options, deadline, userCredentials, cancellationToken); + return SetStreamMetadataInternal( + metadata, + new AppendReq { + Options = new AppendReq.Types.Options { + StreamIdentifier = SystemStreams.MetastreamOf(streamName) + } + }.WithAnyStreamRevision(expectedState), + operationOptions, + cancellationToken + ); } - private async Task SetStreamMetadataInternal(StreamMetadata metadata, + async Task SetStreamMetadataInternal( + StreamMetadata metadata, AppendReq appendReq, - KurrentDBClientOperationOptions operationOptions, - TimeSpan? deadline, - UserCredentials? userCredentials, - CancellationToken cancellationToken) { - + SetStreamMetadataOptions operationOptions, + CancellationToken cancellationToken + ) { var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - return await AppendToStreamInternal(channelInfo, appendReq, new[] { - new EventData(Uuid.NewUuid(), SystemEventTypes.StreamMetadata, - JsonSerializer.SerializeToUtf8Bytes(metadata, StreamMetadataJsonSerializerOptions)), - }, operationOptions, deadline, userCredentials, cancellationToken).ConfigureAwait(false); + return await AppendToStreamInternal( + channelInfo, + appendReq, + [ + new MessageData( + SystemEventTypes.StreamMetadata, + JsonSerializer.SerializeToUtf8Bytes(metadata, StreamMetadataJsonSerializerOptions) + ) + ], + operationOptions, + cancellationToken + ).ConfigureAwait(false); + } + } + + public static class KurrentDBClientMetadataObsoleteExtensions { + /// + /// Asynchronously reads the metadata for a stream + /// + /// + /// The name of the stream to read the metadata for. + /// + /// The optional to perform operation with. + /// The optional . + /// + [Obsolete("Use method with OperationOptions parameter")] + public static Task GetStreamMetadataAsync( + this KurrentDBClient dbClient, + string streamName, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) => + dbClient.GetStreamMetadataAsync( + streamName, + new OperationOptions { Deadline = deadline, UserCredentials = userCredentials }, + cancellationToken + ); + + /// + /// Asynchronously sets the metadata for a stream. + /// + /// + /// The name of the stream to set metadata for. + /// The of the stream to append to. + /// A representing the new metadata. + /// An to configure the operation's options. + /// + /// The optional to perform operation with. + /// The optional . + /// + [Obsolete("Use method with SetStreamMetadataOptions parameter")] + public static Task SetStreamMetadataAsync( + this KurrentDBClient dbClient, + string streamName, + StreamState expectedState, + StreamMetadata metadata, + Action? configureOperationOptions = null, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) { + var operationOptions = + new SetStreamMetadataOptions { Deadline = deadline, UserCredentials = userCredentials }; + configureOperationOptions?.Invoke(operationOptions); + + return dbClient.SetStreamMetadataAsync( + streamName, + expectedState, + metadata, + operationOptions, + cancellationToken + ); } } + + public class SetStreamMetadataOptions : AppendToStreamOptions; } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs index de1861a25..400de7ade 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Read.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using System.Threading.Channels; using EventStore.Client.Streams; using Grpc.Core; @@ -14,9 +15,11 @@ public partial class KurrentDBClient { /// The optional . /// public ReadAllStreamResult ReadAllAsync( - ReadAllOptions options, + ReadAllOptions? options = null, CancellationToken cancellationToken = default ) { + options ??= new ReadAllOptions(); + if (options.MaxCount <= 0) throw new ArgumentOutOfRangeException(nameof(options.MaxCount)); @@ -25,16 +28,17 @@ public ReadAllStreamResult ReadAllAsync( ReadDirection = options.Direction switch { Direction.Backwards => ReadReq.Types.Options.Types.ReadDirection.Backwards, Direction.Forwards => ReadReq.Types.Options.Types.ReadDirection.Forwards, - _ => throw InvalidOption(options.Direction) + null => ReadReq.Types.Options.Types.ReadDirection.Forwards, + _ => throw InvalidOption(options.Direction.Value) }, - ResolveLinks = options.ResolveLinkTos, + ResolveLinks = options.ResolveLinkTos ?? false, All = new() { Position = new() { - CommitPosition = options.Position.CommitPosition, - PreparePosition = options.Position.PreparePosition + CommitPosition = (options.Position ?? Position.Start).CommitPosition, + PreparePosition = (options.Position ?? Position.Start).PreparePosition } }, - Count = (ulong)options.MaxCount, + Count = (ulong)(options.MaxCount ?? long.MaxValue), UuidOption = new() { Structured = new() }, ControlOption = new() { Compatibility = 1 }, Filter = GetFilterOptions(options.Filter) @@ -49,81 +53,7 @@ public ReadAllStreamResult ReadAllAsync( readReq, Settings, options, - _messageSerializer.With(Settings.Serialization, options.SerializationSettings), - cancellationToken - ); - } - - /// - /// Asynchronously reads all events. - /// - /// The in which to read. - /// The to start reading from. - /// The maximum count to read. - /// Whether to resolve LinkTo events automatically. - /// - /// The optional to perform operation with. - /// The optional . - /// - public ReadAllStreamResult ReadAllAsync( - Direction direction, - Position position, - long maxCount = long.MaxValue, - bool resolveLinkTos = false, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) => - ReadAllAsync( - new ReadAllOptions { - Direction = direction, - Position = position, - Filter = null, - MaxCount = maxCount, - ResolveLinkTos = resolveLinkTos, - Deadline = deadline, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - - /// - /// Asynchronously reads all events with filtering. - /// - /// The in which to read. - /// The to start reading from. - /// The to apply. - /// The maximum count to read. - /// Whether to resolve LinkTo events automatically. - /// - /// The optional to perform operation with. - /// The optional . - /// - public ReadAllStreamResult ReadAllAsync( - Direction direction, - Position position, - IEventFilter? eventFilter, - long maxCount = long.MaxValue, - bool resolveLinkTos = false, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) { - if (maxCount <= 0) - throw new ArgumentOutOfRangeException(nameof(maxCount)); - - return ReadAllAsync( - new ReadAllOptions { - Direction = direction, - Position = position, - Filter = eventFilter, - MaxCount = maxCount, - ResolveLinkTos = resolveLinkTos, - Deadline = deadline, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, + _messageSerializer.With(options.SerializationSettings), cancellationToken ); } @@ -266,9 +196,11 @@ public async IAsyncEnumerator GetAsyncEnumerator( /// public ReadStreamResult ReadStreamAsync( string streamName, - ReadStreamOptions options, + ReadStreamOptions? options = null, CancellationToken cancellationToken = default ) { + options ??= new ReadStreamOptions(); + if (options.MaxCount <= 0) throw new ArgumentOutOfRangeException(nameof(options.MaxCount)); @@ -282,14 +214,15 @@ public ReadStreamResult ReadStreamAsync( ReadDirection = options.Direction switch { Direction.Backwards => ReadReq.Types.Options.Types.ReadDirection.Backwards, Direction.Forwards => ReadReq.Types.Options.Types.ReadDirection.Forwards, - _ => throw InvalidOption(options.Direction) + null => ReadReq.Types.Options.Types.ReadDirection.Forwards, + _ => throw InvalidOption(options.Direction.Value) }, - ResolveLinks = options.ResolveLinkTos, + ResolveLinks = options.ResolveLinkTos ?? false, Stream = ReadReq.Types.Options.Types.StreamOptions.FromStreamNameAndRevision( streamName, - options.StreamPosition + options.StreamPosition ?? StreamPosition.Start ), - Count = (ulong)options.MaxCount, + Count = (ulong)(options.MaxCount ?? long.MaxValue), UuidOption = new() { Structured = new() }, NoFilter = new(), ControlOption = new() { Compatibility = 1 } @@ -298,49 +231,7 @@ public ReadStreamResult ReadStreamAsync( Settings, options.Deadline, options.UserCredentials, - _messageSerializer.With(Settings.Serialization, options.SerializationSettings), - cancellationToken - ); - } - - /// - /// Asynchronously reads all the events from a stream. - /// - /// The result could also be inspected as a means to avoid handling exceptions as the would indicate whether or not the stream is readable./> - /// - /// The in which to read. - /// The name of the stream to read. - /// The to start reading from. - /// The number of events to read from the stream. - /// Whether to resolve LinkTo events automatically. - /// - /// The optional to perform operation with. - /// The optional . - /// - public ReadStreamResult ReadStreamAsync( - Direction direction, - string streamName, - StreamPosition revision, - long maxCount = long.MaxValue, - bool resolveLinkTos = false, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) { - if (maxCount <= 0) - throw new ArgumentOutOfRangeException(nameof(maxCount)); - - return ReadStreamAsync( - streamName, - new ReadStreamOptions { - Direction = direction, - StreamPosition = revision, - MaxCount = maxCount, - ResolveLinkTos = resolveLinkTos, - Deadline = deadline, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, + _messageSerializer.With(options.SerializationSettings), cancellationToken ); } @@ -552,16 +443,16 @@ IMessageSerializer messageSerializer /// Optional settings to customize reading all messages, for instance: max count, /// in which to read, the to start reading from, etc. /// - public class ReadAllOptions { + public class ReadAllOptions : OperationOptions { /// - /// The in which to read. + /// The in which to read. When not provided Forwards is used. /// - public Direction Direction { get; set; } = Direction.Forwards; + public Direction? Direction { get; set; } /// - /// The to start reading from. + /// The to start reading from. When not provided Start is used. /// - public Position Position { get; set; } = Position.Start; + public Position? Position { get; set; } /// /// The to apply. @@ -569,84 +460,281 @@ public class ReadAllOptions { public IEventFilter? Filter { get; set; } /// - /// The number of events to read from the stream. - /// - public long MaxCount { get; set; } = long.MaxValue; - - /// - /// Whether to resolve LinkTo events automatically. + /// The number of events to read from the stream. When not provided, no limit is set. /// - public bool ResolveLinkTos { get; set; } + public long? MaxCount { get; set; } /// - /// Maximum time that the operation will be run + /// Whether to resolve LinkTo events automatically. When not provided, false is used. /// - public TimeSpan? Deadline { get; set; } - - /// - /// The optional to perform operation with. - /// - public UserCredentials? UserCredentials { get; set; } + public bool? ResolveLinkTos { get; set; } /// /// Allows to customize or disable the automatic deserialization /// public OperationSerializationSettings? SerializationSettings { get; set; } + + public static ReadAllOptions Get() => + new ReadAllOptions(); + + public ReadAllOptions WithFilter(IEventFilter filter) { + Filter = filter; + + return this; + } + + public ReadAllOptions Forwards() { + Direction = KurrentDB.Client.Direction.Forwards; + Position ??= KurrentDB.Client.Position.Start; + + return this; + } + + public ReadAllOptions Backwards() { + Direction = KurrentDB.Client.Direction.Backwards; + Position ??= KurrentDB.Client.Position.End; + + return this; + } + + public ReadAllOptions From(Position position) { + this.Position = position; + + return this; + } + + public ReadAllOptions FromStart() { + Position = KurrentDB.Client.Position.Start; + Direction ??= Client.Direction.Forwards; + + return this; + } + + public ReadAllOptions FromEnd() { + Position = KurrentDB.Client.Position.End; + Direction ??= Client.Direction.Backwards; + + return this; + } + + public ReadAllOptions WithResolveLinkTos(bool resolve = true) { + ResolveLinkTos = resolve; + + return this; + } + + public ReadAllOptions Max(long maxCount) { + MaxCount = maxCount; + + return this; + } + + public ReadAllOptions MaxOne() => + Max(1); + + public ReadAllOptions First() => + FromStart() + .Forwards() + .MaxOne(); + + public ReadAllOptions Last() => + FromEnd() + .Backwards() + .MaxOne(); + + public ReadAllOptions DisableAutoSerialization() { + SerializationSettings = OperationSerializationSettings.Disabled; + + return this; + } } /// /// Optional settings to customize reading stream messages, for instance: max count, /// in which to read, the to start reading from, etc. /// - public class ReadStreamOptions { + public class ReadStreamOptions : OperationOptions { /// /// The in which to read. /// - public Direction Direction { get; set; } = Direction.Forwards; + public Direction? Direction { get; set; } /// /// The to start reading from. /// - public StreamPosition StreamPosition { get; set; } = StreamPosition.Start; + public StreamPosition? StreamPosition { get; set; } /// /// The number of events to read from the stream. /// - public long MaxCount { get; set; } = long.MaxValue; + public long? MaxCount { get; set; } /// /// Whether to resolve LinkTo events automatically. /// - public bool ResolveLinkTos { get; set; } - - /// - /// Maximum time that the operation will be run - /// - public TimeSpan? Deadline { get; set; } - - /// - /// The optional to perform operation with. - /// - public UserCredentials? UserCredentials { get; set; } + public bool? ResolveLinkTos { get; set; } /// /// Allows to customize or disable the automatic deserialization /// public OperationSerializationSettings? SerializationSettings { get; set; } + + public static ReadStreamOptions Get() => + new ReadStreamOptions(); + + public ReadStreamOptions Forwards() { + Direction = KurrentDB.Client.Direction.Forwards; + StreamPosition ??= KurrentDB.Client.StreamPosition.Start; + + return this; + } + + public ReadStreamOptions Backwards() { + Direction = KurrentDB.Client.Direction.Backwards; + StreamPosition ??= KurrentDB.Client.StreamPosition.End; + + return this; + } + + public ReadStreamOptions From(StreamPosition streamPosition) { + StreamPosition = streamPosition; + + return this; + } + + public ReadStreamOptions FromStart() { + StreamPosition = KurrentDB.Client.StreamPosition.Start; + Direction ??= Client.Direction.Forwards; + + return this; + } + + public ReadStreamOptions FromEnd() { + StreamPosition = KurrentDB.Client.StreamPosition.End; + Direction ??= Client.Direction.Backwards; + + return this; + } + + public ReadStreamOptions WithResolveLinkTos(bool resolve = true) { + ResolveLinkTos = resolve; + + return this; + } + + public ReadStreamOptions Max(long maxCount) { + MaxCount = maxCount; + + return this; + } + + public ReadStreamOptions MaxOne() => + Max(1); + + public ReadStreamOptions First() => + FromStart() + .Forwards() + .MaxOne(); + + public ReadStreamOptions Last() => + FromEnd() + .Backwards() + .MaxOne(); + + public ReadStreamOptions DisableAutoSerialization() { + SerializationSettings = OperationSerializationSettings.Disabled; + + return this; + } } - public static class KurrentDBClientReadExtensions { + [Obsolete("Those extensions may be removed in the future versions", false)] + public static class ObsoleteKurrentDBClientReadExtensions { /// - /// Asynchronously reads all events. By default, it reads all of them from the start. The options parameter allows you to fine-tune it to your needs. + /// Asynchronously reads all events. /// /// + /// The in which to read. + /// The to start reading from. + /// The maximum count to read. + /// Whether to resolve LinkTo events automatically. + /// + /// The optional to perform operation with. /// The optional . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with ReadAllOptions and get auto-serialization capabilities", + false + )] public static KurrentDBClient.ReadAllStreamResult ReadAllAsync( this KurrentDBClient dbClient, + Direction direction, + Position position, + long maxCount = long.MaxValue, + bool resolveLinkTos = false, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default ) => - dbClient.ReadAllAsync(new ReadAllOptions(), cancellationToken); + dbClient.ReadAllAsync( + new ReadAllOptions { + Direction = direction, + Position = position, + Filter = null, + MaxCount = maxCount, + ResolveLinkTos = resolveLinkTos, + Deadline = deadline, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, + cancellationToken + ); + + /// + /// Asynchronously reads all events with filtering. + /// + /// + /// The in which to read. + /// The to start reading from. + /// The to apply. + /// The maximum count to read. + /// Whether to resolve LinkTo events automatically. + /// + /// The optional to perform operation with. + /// The optional . + /// + [Obsolete( + "This method may be removed in future releases. Use the overload with ReadAllOptions and get auto-serialization capabilities", + false + )] + public static KurrentDBClient.ReadAllStreamResult ReadAllAsync( + this KurrentDBClient dbClient, + Direction direction, + Position position, + IEventFilter? eventFilter, + long maxCount = long.MaxValue, + bool resolveLinkTos = false, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) { + if (maxCount <= 0) + throw new ArgumentOutOfRangeException(nameof(maxCount)); + + return dbClient.ReadAllAsync( + new ReadAllOptions { + Direction = direction, + Position = position, + Filter = eventFilter, + MaxCount = maxCount, + ResolveLinkTos = resolveLinkTos, + Deadline = deadline, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, + cancellationToken + ); + } /// /// Asynchronously reads all the events from a stream. @@ -654,19 +742,68 @@ public static KurrentDBClient.ReadAllStreamResult ReadAllAsync( /// The result could also be inspected as a means to avoid handling exceptions as the would indicate whether or not the stream is readable./> /// /// + /// The in which to read. /// The name of the stream to read. - /// Optional settings like: max count, in which to read, the to start reading from, etc. + /// The to start reading from. + /// The number of events to read from the stream. + /// Whether to resolve LinkTo events automatically. + /// + /// The optional to perform operation with. /// The optional . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with ReadStreamOptions and get auto-serialization capabilities", + false + )] public static KurrentDBClient.ReadStreamResult ReadStreamAsync( this KurrentDBClient dbClient, + Direction direction, string streamName, + StreamPosition revision, + long maxCount = long.MaxValue, + bool resolveLinkTos = false, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default - ) => - dbClient.ReadStreamAsync( + ) { + if (maxCount <= 0) + throw new ArgumentOutOfRangeException(nameof(maxCount)); + + return dbClient.ReadStreamAsync( streamName, - new ReadStreamOptions(), + new ReadStreamOptions { + Direction = direction, + StreamPosition = revision, + MaxCount = maxCount, + ResolveLinkTos = resolveLinkTos, + Deadline = deadline, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); + } + } + + public static class ReadMessagesExtensions { + public static async IAsyncEnumerable DeserializedData( + this IAsyncEnumerable resolvedEvents, + [EnumeratorCancellation] CancellationToken ct = default + ) { + await foreach (var resolvedEvent in resolvedEvents.WithCancellation(ct)) { + if (resolvedEvent.DeserializedData != null) + yield return resolvedEvent.DeserializedData; + } + } + + public static async IAsyncEnumerable DeserializedMessages( + this IAsyncEnumerable resolvedEvents, + [EnumeratorCancellation] CancellationToken ct = default + ) { + await foreach (var resolvedEvent in resolvedEvents.WithCancellation(ct)) { + if (resolvedEvent.Message != null) + yield return resolvedEvent.Message; + } + } } } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs index c4aef8879..28d72d3cd 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Subscriptions.cs @@ -6,145 +6,7 @@ using static EventStore.Client.Streams.ReadResp.ContentOneofCase; namespace KurrentDB.Client { - /// - /// Subscribes to all events options. - /// - public class SubscribeToAllOptions { - /// - /// A (exclusive of) to start the subscription from. - /// - public FromAll Start { get; set; } = FromAll.Start; - - /// - /// Whether to resolve LinkTo events automatically. - /// - public bool ResolveLinkTos { get; set; } - - /// - /// The optional to apply. - /// - public SubscriptionFilterOptions? FilterOptions { get; set; } - - /// - /// The optional to apply. - /// - public IEventFilter Filter { set => FilterOptions = new SubscriptionFilterOptions(value); } - - /// - /// The optional user credentials to perform operation with. - /// - public UserCredentials? UserCredentials { get; set; } - - /// - /// Allows to customize or disable the automatic deserialization - /// - public OperationSerializationSettings? SerializationSettings { get; set; } - } - - /// - /// Subscribes to all events options. - /// - public class SubscribeToStreamOptions { - /// - /// A (exclusive of) to start the subscription from. - /// - public FromStream Start { get; set; } = FromStream.Start; - - /// - /// Whether to resolve LinkTo events automatically. - /// - public bool ResolveLinkTos { get; set; } - - /// - /// The optional user credentials to perform operation with. - /// - public UserCredentials? UserCredentials { get; set; } - - /// - /// Allows to customize or disable the automatic deserialization - /// - public OperationSerializationSettings? SerializationSettings { get; set; } - } - - public class SubscriptionListener { -#if NET48 - /// - /// A handler called when a new event is received over the subscription. - /// - public Func EventAppeared { get; set; } = null!; -#else - public required Func EventAppeared { get; set; } -#endif - /// - /// A handler called if the subscription is dropped. - /// - public Action? SubscriptionDropped { get; set; } - - /// - /// A handler called when a checkpoint is reached. - /// Set the checkpointInterval in subscription filter options to define how often this method is called. - /// - public Func? CheckpointReached { get; set; } - - /// - /// Returns the subscription listener with configured handlers - /// - /// Handler invoked when a new event is received over the subscription. - /// A handler invoked if the subscription is dropped. - /// A handler called when a checkpoint is reached. - /// Set the checkpointInterval in subscription filter options to define how often this method is called. - /// - /// - public static SubscriptionListener Handle( - Func eventAppeared, - Action? subscriptionDropped = null, - Func? checkpointReached = null - ) => - new SubscriptionListener { - EventAppeared = eventAppeared, - SubscriptionDropped = subscriptionDropped, - CheckpointReached = checkpointReached - }; - } - public partial class KurrentDBClient { - /// - /// 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. - /// 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( - FromAll start, - Func eventAppeared, - bool resolveLinkTos = false, - Action? subscriptionDropped = default, - SubscriptionFilterOptions? filterOptions = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) { - var listener = SubscriptionListener.Handle( - eventAppeared, - subscriptionDropped, - filterOptions?.CheckpointReached - ); - - var options = new SubscribeToAllOptions { - Start = start, - FilterOptions = filterOptions, - ResolveLinkTos = resolveLinkTos, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled, - }; - - return SubscribeToAllAsync(listener, options, cancellationToken); - } - /// /// Subscribes to all events. /// @@ -154,9 +16,10 @@ public Task SubscribeToAllAsync( /// public Task SubscribeToAllAsync( SubscriptionListener listener, - SubscribeToAllOptions options, + SubscribeToAllOptions? options = null, CancellationToken cancellationToken = default ) { + options ??= new SubscribeToAllOptions(); listener.CheckpointReached ??= options.FilterOptions?.CheckpointReached; return StreamSubscription.Confirm( @@ -174,84 +37,29 @@ public Task SubscribeToAllAsync( /// The optional . /// public StreamSubscriptionResult SubscribeToAll( - SubscribeToAllOptions options, - CancellationToken cancellationToken = default - ) => new( - async _ => await GetChannelInfo(cancellationToken).ConfigureAwait(false), - new ReadReq { - Options = new ReadReq.Types.Options { - ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, - ResolveLinks = options.ResolveLinkTos, - All = ReadReq.Types.Options.Types.AllOptions.FromSubscriptionPosition(options.Start), - Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), - Filter = GetFilterOptions(options.FilterOptions)!, - UuidOption = new() { Structured = new() } - } - }, - Settings, - options.UserCredentials, - _messageSerializer.With(Settings.Serialization, options.SerializationSettings), - cancellationToken - ); - - /// - /// Subscribes to all events. - /// - /// A (exclusive of) to start the subscription from. - /// Whether to resolve LinkTo events automatically. - /// The optional to apply. - /// The optional user credentials to perform operation with. - /// The optional . - /// - public StreamSubscriptionResult SubscribeToAll( - FromAll start, - bool resolveLinkTos = false, - SubscriptionFilterOptions? filterOptions = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default - ) => - SubscribeToAll( - new SubscribeToAllOptions { - Start = start, - ResolveLinkTos = resolveLinkTos, - FilterOptions = filterOptions, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled - }, - cancellationToken - ); - - /// - /// Subscribes to a stream from a checkpoint. - /// - /// A (exclusive of) to start the subscription from. - /// The name of the stream to subscribe for notifications about new events. - /// A Task invoked and awaited when a new event is received over the subscription. - /// 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, - FromStream start, - Func eventAppeared, - bool resolveLinkTos = false, - Action? subscriptionDropped = default, - UserCredentials? userCredentials = null, + SubscribeToAllOptions? options = null, CancellationToken cancellationToken = default - ) => - SubscribeToStreamAsync( - streamName, - SubscriptionListener.Handle(eventAppeared, subscriptionDropped), - new SubscribeToStreamOptions { - Start = start, - ResolveLinkTos = resolveLinkTos, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled + ) { + options ??= new SubscribeToAllOptions(); + + return new StreamSubscriptionResult( + async _ => await GetChannelInfo(cancellationToken).ConfigureAwait(false), + new ReadReq { + Options = new ReadReq.Types.Options { + ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, + ResolveLinks = options.ResolveLinkTos ?? false, + All = ReadReq.Types.Options.Types.AllOptions.FromSubscriptionPosition(options.Start ?? FromAll.Start), + Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), + Filter = GetFilterOptions(options.FilterOptions)!, + UuidOption = new() { Structured = new() } + } }, + Settings, + options.UserCredentials, + _messageSerializer.With(options.SerializationSettings), cancellationToken ); + } /// /// Subscribes to a stream from a checkpoint. @@ -264,87 +72,63 @@ public Task SubscribeToStreamAsync( public Task SubscribeToStreamAsync( string streamName, SubscriptionListener listener, - SubscribeToStreamOptions options, + SubscribeToStreamOptions? options = null, CancellationToken cancellationToken = default - ) { - return StreamSubscription.Confirm( + ) => + StreamSubscription.Confirm( SubscribeToStream(streamName, options, cancellationToken), listener, _log, cancellationToken ); - } /// /// Subscribes to a stream from a checkpoint. /// - /// A (exclusive of) to start the subscription from. /// The name of the stream to subscribe for notifications about new events. - /// Whether to resolve LinkTo events automatically. - /// The optional user credentials to perform operation with. + /// Optional settings like: Position from which to read, etc. /// The optional . /// public StreamSubscriptionResult SubscribeToStream( string streamName, - FromStream start, - bool resolveLinkTos = false, - UserCredentials? userCredentials = null, + SubscribeToStreamOptions? options = null, CancellationToken cancellationToken = default - ) => - SubscribeToStream( - streamName, - new SubscribeToStreamOptions { - Start = start, - ResolveLinkTos = resolveLinkTos, - UserCredentials = userCredentials, - SerializationSettings = OperationSerializationSettings.Disabled + ) { + options ??= new SubscribeToStreamOptions(); + + return new StreamSubscriptionResult( + async _ => await GetChannelInfo(cancellationToken).ConfigureAwait(false), + new ReadReq { + Options = new ReadReq.Types.Options { + ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, + ResolveLinks = options.ResolveLinkTos ?? false, + Stream = ReadReq.Types.Options.Types.StreamOptions.FromSubscriptionPosition( + streamName, + options.Start ?? FromStream.Start + ), + Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), + UuidOption = new() { Structured = new() } + } }, + Settings, + options.UserCredentials, + _messageSerializer.With(options.SerializationSettings), cancellationToken ); - - /// - /// Subscribes to a stream from a checkpoint. - /// - /// The name of the stream to subscribe for notifications about new events. - /// Optional settings like: Position from which to read, etc. - /// The optional . - /// - public StreamSubscriptionResult SubscribeToStream( - string streamName, - SubscribeToStreamOptions options, - CancellationToken cancellationToken = default - ) => new( - async _ => await GetChannelInfo(cancellationToken).ConfigureAwait(false), - new ReadReq { - Options = new ReadReq.Types.Options { - ReadDirection = ReadReq.Types.Options.Types.ReadDirection.Forwards, - ResolveLinks = options.ResolveLinkTos, - Stream = ReadReq.Types.Options.Types.StreamOptions.FromSubscriptionPosition( - streamName, - options.Start - ), - Subscription = new ReadReq.Types.Options.Types.SubscriptionOptions(), - UuidOption = new() { Structured = new() } - } - }, - Settings, - options.UserCredentials, - _messageSerializer.With(Settings.Serialization, options.SerializationSettings), - cancellationToken - ); + } /// /// A class that represents the result of a subscription operation. You may either enumerate this instance directly or . Do not enumerate more than once. /// public class StreamSubscriptionResult : IAsyncEnumerable, IAsyncDisposable, IDisposable { - private readonly ReadReq _request; - private readonly Channel _channel; - private readonly CancellationTokenSource _cts; - private readonly CallOptions _callOptions; - private readonly KurrentDBClientSettings _settings; - private AsyncServerStreamingCall? _call; + readonly ReadReq _request; + readonly Channel _channel; + readonly CancellationTokenSource _cts; + readonly CallOptions _callOptions; + readonly KurrentDBClientSettings _settings; + AsyncServerStreamingCall? _call; - private int _messagesEnumerated; + int _messagesEnumerated; /// /// The server-generated unique identifier for the subscription. @@ -521,150 +305,377 @@ public async IAsyncEnumerator GetAsyncEnumerator( } } - public static class KurrentDBClientSubscribeToAllExtensions { + /// + /// Subscribes to all events options. + /// + public class SubscribeToAllOptions { /// - /// Subscribes to all events. + /// A (exclusive of) to start the subscription from. /// - /// - /// Listener configured to receive notifications about new events and subscription state change. - /// The optional . + public FromAll? Start { get; set; } + + /// + /// Whether to resolve LinkTo events automatically. + /// + public bool? ResolveLinkTos { get; set; } + + /// + /// The optional to apply. + /// + public SubscriptionFilterOptions? FilterOptions { get; set; } + + /// + /// The optional to apply. + /// + public IEventFilter Filter { set => FilterOptions = new SubscriptionFilterOptions(value); } + + /// + /// The optional user credentials to perform operation with. + /// + public UserCredentials? UserCredentials { get; set; } + + /// + /// Allows to customize or disable the automatic deserialization + /// + public OperationSerializationSettings? SerializationSettings { get; set; } + + public static SubscribeToAllOptions Get() => + new SubscribeToAllOptions(); + + public SubscribeToAllOptions WithFilter(SubscriptionFilterOptions filter) { + FilterOptions = filter; + + return this; + } + + public SubscribeToAllOptions WithFilter(IEventFilter filter) { + Filter = filter; + + return this; + } + + public SubscribeToAllOptions From(FromAll position) { + Start = position; + + return this; + } + + public SubscribeToAllOptions FromStart() { + Start = FromAll.Start; + + return this; + } + + public SubscribeToAllOptions FromEnd() { + Start = FromAll.End; + + return this; + } + + public SubscribeToAllOptions WithResolveLinkTos(bool resolve = true) { + ResolveLinkTos = resolve; + + return this; + } + + public SubscribeToAllOptions DisableAutoSerialization() { + SerializationSettings = OperationSerializationSettings.Disabled; + + return this; + } + } + + /// + /// Subscribes to all events options. + /// + public class SubscribeToStreamOptions { + /// + /// A (exclusive of) to start the subscription from. + /// + public FromStream? Start { get; set; } + + /// + /// Whether to resolve LinkTo events automatically. + /// + public bool? ResolveLinkTos { get; set; } + + /// + /// The optional user credentials to perform operation with. + /// + public UserCredentials? UserCredentials { get; set; } + + /// + /// Allows to customize or disable the automatic deserialization + /// + public OperationSerializationSettings? SerializationSettings { get; set; } + + public static SubscribeToStreamOptions Get() => + new SubscribeToStreamOptions(); + + public SubscribeToStreamOptions From(FromStream position) { + Start = position; + + return this; + } + + public SubscribeToStreamOptions FromStart() { + Start = FromStream.Start; + + return this; + } + + public SubscribeToStreamOptions FromEnd() { + Start = FromStream.End; + + return this; + } + + public SubscribeToStreamOptions WithResolveLinkTos(bool resolve = true) { + ResolveLinkTos = resolve; + + return this; + } + + public SubscribeToStreamOptions DisableAutoSerialization() { + SerializationSettings = OperationSerializationSettings.Disabled; + + return this; + } + } + + public class SubscriptionListener { +#if NET48 + /// + /// A handler called when a new event is received over the subscription. + /// + public Func EventAppeared { get; set; } = null!; +#else + public required Func EventAppeared { get; set; } +#endif + /// + /// A handler called if the subscription is dropped. + /// + public Action? SubscriptionDropped { get; set; } + + /// + /// A handler called when a checkpoint is reached. + /// Set the checkpointInterval in subscription filter options to define how often this method is called. + /// + public Func? CheckpointReached { get; set; } + + /// + /// Returns the subscription listener with configured handlers + /// + /// Handler invoked when a new event is received over the subscription. + /// A handler invoked if the subscription is dropped. + /// A handler called when a checkpoint is reached. + /// Set the checkpointInterval in subscription filter options to define how often this method is called. + /// /// - public static Task SubscribeToAllAsync( - this KurrentDBClient kurrentDbClient, - SubscriptionListener listener, - CancellationToken cancellationToken = default + public static SubscriptionListener Handle( + Func eventAppeared, + Action? subscriptionDropped = null, + Func? checkpointReached = null ) => - kurrentDbClient.SubscribeToAllAsync(listener, new SubscribeToAllOptions(), cancellationToken); + new SubscriptionListener { + EventAppeared = eventAppeared, + SubscriptionDropped = subscriptionDropped, + CheckpointReached = checkpointReached + }; + } + public static class KurrentDBClientSubscribeExtensions { /// /// Subscribes to all events. /// /// - /// + /// Handler invoked when a new event is received over the subscription. + /// Optional settings like: Position from which to read, to apply, etc. /// The optional . /// public static Task SubscribeToAllAsync( this KurrentDBClient kurrentDbClient, Func eventAppeared, + SubscribeToAllOptions? options = null, CancellationToken cancellationToken = default ) => kurrentDbClient.SubscribeToAllAsync( - eventAppeared, - new SubscribeToAllOptions(), + SubscriptionListener.Handle(eventAppeared), + options, cancellationToken ); /// - /// Subscribes to all events. + /// Subscribes to messages from a specific stream /// /// + /// The name of the stream to subscribe for notifications about new events. /// Handler invoked when a new event is received over the subscription. /// Optional settings like: Position from which to read, to apply, etc. /// The optional . /// - public static Task SubscribeToAllAsync( + public static Task SubscribeToStreamAsync( this KurrentDBClient kurrentDbClient, + string streamName, Func eventAppeared, - SubscribeToAllOptions options, + SubscribeToStreamOptions? options = null, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToAllAsync( + kurrentDbClient.SubscribeToStreamAsync( + streamName, SubscriptionListener.Handle(eventAppeared), options, cancellationToken ); + } + [Obsolete("Those extensions may be removed in the future versions", false)] + public static class KurrentDBClientObsoleteSubscribeExtensions { /// /// 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. + /// 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 static KurrentDBClient.StreamSubscriptionResult SubscribeToAll( - this KurrentDBClient kurrentDbClient, + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToAllOptions and get auto-serialization capabilities", + false + )] + public static Task SubscribeToAllAsync( + this KurrentDBClient dbClient, + FromAll start, + Func eventAppeared, + bool resolveLinkTos = false, + Action? subscriptionDropped = null, + SubscriptionFilterOptions? filterOptions = null, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default - ) => - kurrentDbClient.SubscribeToAll(new SubscribeToAllOptions(), cancellationToken); - } + ) { + var listener = SubscriptionListener.Handle( + eventAppeared, + subscriptionDropped, + filterOptions?.CheckpointReached + ); + + var options = new SubscribeToAllOptions { + Start = start, + FilterOptions = filterOptions, + ResolveLinkTos = resolveLinkTos, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled, + }; + + return dbClient.SubscribeToAllAsync(listener, options, cancellationToken); + } - public static class KurrentDBClientSubscribeToStreamExtensions { /// - /// Subscribes to messages from a specific stream + /// Subscribes to all events. /// - /// - /// The name of the stream to subscribe for notifications about new events. - /// Listener configured to receive notifications about new events and subscription state change. + /// + /// A (exclusive of) to start the subscription from. + /// Whether to resolve LinkTo events automatically. + /// The optional to apply. + /// The optional user credentials to perform operation with. /// The optional . /// - public static Task SubscribeToStreamAsync( - this KurrentDBClient kurrentDbClient, - string streamName, - SubscriptionListener listener, + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToAllOptions and get auto-serialization capabilities", + false + )] + public static KurrentDBClient.StreamSubscriptionResult SubscribeToAll( + this KurrentDBClient dbClient, + FromAll start, + bool resolveLinkTos = false, + SubscriptionFilterOptions? filterOptions = null, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToStreamAsync( - streamName, - listener, - new SubscribeToStreamOptions(), + dbClient.SubscribeToAll( + new SubscribeToAllOptions { + Start = start, + ResolveLinkTos = resolveLinkTos, + FilterOptions = filterOptions, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); /// - /// Subscribes to messages from a specific stream + /// Subscribes to a stream from a checkpoint. /// - /// + /// A (exclusive of) to start the subscription from. + /// /// The name of the stream to subscribe for notifications about new events. - /// + /// Whether to resolve LinkTo events automatically. + /// The optional user credentials to perform operation with. /// The optional . /// - public static Task SubscribeToStreamAsync( - this KurrentDBClient kurrentDbClient, + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToStreamOptions and get auto-serialization capabilities", + false + )] + public static KurrentDBClient.StreamSubscriptionResult SubscribeToStream( + this KurrentDBClient dbClient, string streamName, - Func eventAppeared, + FromStream start, + bool resolveLinkTos = false, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToStreamAsync( + dbClient.SubscribeToStream( streamName, - eventAppeared, - new SubscribeToStreamOptions(), + new SubscribeToStreamOptions { + Start = start, + ResolveLinkTos = resolveLinkTos, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); /// - /// Subscribes to messages from a specific stream + /// Subscribes to a stream from a checkpoint. /// - /// + /// A (exclusive of) to start the subscription from. + /// /// The name of the stream to subscribe for notifications about new events. - /// Handler invoked when a new event is received over the subscription. - /// Optional settings like: Position from which to read, to apply, etc. + /// A Task invoked and awaited when a new event is received over the subscription. + /// Whether to resolve LinkTo events automatically. + /// An action invoked if the subscription is dropped. + /// The optional user credentials to perform operation with. /// The optional . /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SubscribeToStreamOptions and get auto-serialization capabilities", + false + )] public static Task SubscribeToStreamAsync( - this KurrentDBClient kurrentDbClient, + this KurrentDBClient dbClient, string streamName, + FromStream start, Func eventAppeared, - SubscribeToStreamOptions options, + bool resolveLinkTos = false, + Action? subscriptionDropped = default, + UserCredentials? userCredentials = null, CancellationToken cancellationToken = default ) => - kurrentDbClient.SubscribeToStreamAsync( + dbClient.SubscribeToStreamAsync( streamName, - SubscriptionListener.Handle(eventAppeared), - options, + SubscriptionListener.Handle(eventAppeared, subscriptionDropped), + new SubscribeToStreamOptions { + Start = start, + ResolveLinkTos = resolveLinkTos, + UserCredentials = userCredentials, + SerializationSettings = OperationSerializationSettings.Disabled + }, cancellationToken ); - - /// - /// Subscribes to messages from a specific stream - /// - /// - /// The name of the stream to subscribe for notifications about new events. - /// The optional . - /// - public static KurrentDBClient.StreamSubscriptionResult SubscribeToStream( - this KurrentDBClient kurrentDbClient, - string streamName, - CancellationToken cancellationToken = default - ) => - kurrentDbClient.SubscribeToStream(streamName, new SubscribeToStreamOptions(), cancellationToken); } } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.Tombstone.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.Tombstone.cs index 5957066f6..b97a92426 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.Tombstone.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.Tombstone.cs @@ -8,32 +8,80 @@ public partial class KurrentDBClient { /// /// The name of the stream to tombstone. /// The expected of the stream being deleted. - /// - /// The optional to perform operation with. + /// Optional settings for the tombstone operation, e.g. deadline, user credentials etc. /// The optional . /// public Task TombstoneAsync( string streamName, StreamState expectedState, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) => TombstoneInternal(new TombstoneReq { - Options = new TombstoneReq.Types.Options { - StreamIdentifier = streamName - } - }.WithAnyStreamRevision(expectedState), deadline, userCredentials, cancellationToken); + TombstoneOptions? options = null, + CancellationToken cancellationToken = default + ) => + TombstoneInternal( + new TombstoneReq { + Options = new TombstoneReq.Types.Options { + StreamIdentifier = streamName + } + }.WithAnyStreamRevision(expectedState), + options, + cancellationToken + ); - private async Task TombstoneInternal(TombstoneReq request, TimeSpan? deadline, - UserCredentials? userCredentials, CancellationToken cancellationToken) { + async Task TombstoneInternal( + TombstoneReq request, + TombstoneOptions? options, + CancellationToken cancellationToken + ) { _log.LogDebug("Tombstoning stream {streamName}.", request.Options.StreamIdentifier); var channelInfo = await GetChannelInfo(cancellationToken).ConfigureAwait(false); - using var call = new EventStore.Client.Streams.Streams.StreamsClient( - channelInfo.CallInvoker).TombstoneAsync(request, - KurrentDBCallOptions.CreateNonStreaming(Settings, deadline, userCredentials, cancellationToken)); + using var call = new Streams.StreamsClient(channelInfo.CallInvoker).TombstoneAsync( + request, + KurrentDBCallOptions.CreateNonStreaming( + Settings, + options?.Deadline, + options?.UserCredentials, + cancellationToken + ) + ); + var result = await call.ResponseAsync.ConfigureAwait(false); return new DeleteResult(new Position(result.Position.CommitPosition, result.Position.PreparePosition)); } } + + [Obsolete("Those extensions may be removed in the future versions", false)] + public static class ObsoleteKurrentDBClientTombstoneExtensions { + /// + /// Tombstones a stream asynchronously. Note: Tombstoned streams can never be recreated. + /// + /// + /// The name of the stream to tombstone. + /// The expected of the stream being deleted. + /// + /// The optional to perform operation with. + /// The optional . + /// + [Obsolete( + "This method may be removed in future releases. Use the overload with TombstoneOptions parameter", + false + )] + public static Task TombstoneAsync( + KurrentDBClient dbClient, + string streamName, + StreamState expectedState, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) => + dbClient.TombstoneAsync( + streamName, + expectedState, + new TombstoneOptions { Deadline = deadline, UserCredentials = userCredentials }, + cancellationToken + ); + } + + public class TombstoneOptions : OperationOptions; } diff --git a/src/KurrentDB.Client/Streams/KurrentDBClient.cs b/src/KurrentDB.Client/Streams/KurrentDBClient.cs index 4e9bc36c2..728897871 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClient.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClient.cs @@ -13,7 +13,7 @@ namespace KurrentDB.Client { /// The client used for operations on streams. /// public sealed partial class KurrentDBClient : KurrentDBClientBase { - static readonly JsonSerializerOptions StreamMetadataJsonSerializerOptions = new() { + internal static readonly JsonSerializerOptions StreamMetadataJsonSerializerOptions = new() { Converters = { StreamMetadataJsonConverter.Instance }, diff --git a/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs b/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs index e677d189c..5c890b3eb 100644 --- a/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs +++ b/src/KurrentDB.Client/Streams/KurrentDBClientExtensions.cs @@ -1,15 +1,11 @@ -using System; -using System.Collections.Generic; using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; namespace KurrentDB.Client { /// /// A set of extension methods for an . /// public static class KurrentDBClientExtensions { - private static readonly JsonSerializerOptions SystemSettingsJsonSerializerOptions = new JsonSerializerOptions { + static readonly JsonSerializerOptions SystemSettingsJsonSerializerOptions = new JsonSerializerOptions { Converters = { SystemSettingsJsonConverter.Instance }, @@ -20,33 +16,67 @@ public static class KurrentDBClientExtensions { /// /// /// - /// - /// + /// Optional settings for the append operation, e.g. deadline, user credentials etc. /// /// /// public static Task SetSystemSettingsAsync( this KurrentDBClient dbClient, SystemSettings settings, - TimeSpan? deadline = null, UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { + SetSystemSettingsOptions? options = null, + CancellationToken cancellationToken = default + ) { if (dbClient == null) throw new ArgumentNullException(nameof(dbClient)); - return dbClient.AppendToStreamAsync(SystemStreams.SettingsStream, StreamState.Any, - new[] { - new EventData(Uuid.NewUuid(), SystemEventTypes.Settings, - JsonSerializer.SerializeToUtf8Bytes(settings, SystemSettingsJsonSerializerOptions)) - }, deadline: deadline, userCredentials: userCredentials, cancellationToken: cancellationToken); + + return dbClient.AppendToStreamAsync( + SystemStreams.SettingsStream, + StreamState.Any, + [ + new MessageData( + SystemEventTypes.Settings, + JsonSerializer.SerializeToUtf8Bytes(settings, SystemSettingsJsonSerializerOptions) + ) + ], + options, + cancellationToken + ); } + /// + /// Writes to the $settings stream. + /// + /// + /// + /// + /// + /// + /// + /// + [Obsolete( + "This method may be removed in future releases. Use the overload with SetSystemSettingsOptions options", + false + )] + public static Task SetSystemSettingsAsync( + this KurrentDBClient dbClient, + SystemSettings settings, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) => + dbClient.SetSystemSettingsAsync( + settings, + new SetSystemSettingsOptions { Deadline = deadline, UserCredentials = userCredentials }, + cancellationToken: cancellationToken + ); + /// /// Appends to a stream conditionally. /// /// /// /// - /// - /// - /// + /// + /// /// /// /// @@ -54,17 +84,24 @@ public static async Task ConditionalAppendToStreamAsync( this KurrentDBClient dbClient, string streamName, StreamState expectedState, - IEnumerable eventData, - TimeSpan? deadline = null, - UserCredentials? userCredentials = null, - CancellationToken cancellationToken = default) { + IEnumerable messageData, + AppendToStreamOptions? options = null, + CancellationToken cancellationToken = default + ) { if (dbClient == null) { throw new ArgumentNullException(nameof(dbClient)); } + try { - var result = await dbClient.AppendToStreamAsync(streamName, expectedState, eventData, - options => options.ThrowOnAppendFailure = false, deadline, userCredentials, cancellationToken) + var result = await dbClient.AppendToStreamAsync( + streamName, + expectedState, + messageData, + options, + cancellationToken + ) .ConfigureAwait(false); + return ConditionalWriteResult.FromWriteResult(result); } catch (StreamDeletedException) { return ConditionalWriteResult.StreamDeleted; @@ -72,5 +109,45 @@ public static async Task ConditionalAppendToStreamAsync( return ConditionalWriteResult.FromWrongExpectedVersion(ex); } } + + /// + /// Appends to a stream conditionally. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + + [Obsolete( + "This method may be removed in future releases. Use the overload with AppendToStreamOptions options", + false + )] + public static Task ConditionalAppendToStreamAsync( + this KurrentDBClient dbClient, + string streamName, + StreamState expectedState, + IEnumerable eventData, + TimeSpan? deadline = null, + UserCredentials? userCredentials = null, + CancellationToken cancellationToken = default + ) => + dbClient.ConditionalAppendToStreamAsync( + streamName, + expectedState, + eventData.Select(e => (MessageData)e), + new AppendToStreamOptions { + ThrowOnAppendFailure = false, + Deadline = deadline, + UserCredentials = userCredentials + }, + cancellationToken + ); } + + public class SetSystemSettingsOptions : AppendToStreamOptions; } diff --git a/src/KurrentDB.Client/Streams/WriteResultExtensions.cs b/src/KurrentDB.Client/Streams/WriteResultExtensions.cs index 52f98b334..64b583090 100644 --- a/src/KurrentDB.Client/Streams/WriteResultExtensions.cs +++ b/src/KurrentDB.Client/Streams/WriteResultExtensions.cs @@ -1,11 +1,16 @@ namespace KurrentDB.Client { - internal static class WriteResultExtensions { - public static IWriteResult OptionallyThrowWrongExpectedVersionException(this IWriteResult writeResult, - KurrentDBClientOperationOptions options) => + static class WriteResultExtensions { + public static IWriteResult OptionallyThrowWrongExpectedVersionException( + this IWriteResult writeResult, + AppendToStreamOptions options + ) => (options.ThrowOnAppendFailure, writeResult) switch { (true, WrongExpectedVersionResult wrongExpectedVersionResult) - => throw new WrongExpectedVersionException(wrongExpectedVersionResult.StreamName, - writeResult.NextExpectedStreamState, wrongExpectedVersionResult.ActualStreamState), + => throw new WrongExpectedVersionException( + wrongExpectedVersionResult.StreamName, + writeResult.NextExpectedStreamState, + wrongExpectedVersionResult.ActualStreamState + ), _ => writeResult }; } diff --git a/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs b/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs index a47f8ae85..a327d2830 100644 --- a/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs +++ b/test/KurrentDB.Client.Tests.Common/Extensions/KurrentDBClientWarmupExtensions.cs @@ -1,5 +1,4 @@ using Grpc.Core; -using KurrentDB.Client; using Polly; using Polly.Contrib.WaitAndRetry; using static System.TimeSpan; @@ -14,7 +13,9 @@ public static class KurrentDBClientWarmupExtensions { /// static readonly IEnumerable DefaultBackoffDelay = Backoff.ConstantBackoff(FromMilliseconds(100), 300); - static async Task TryWarmUp(T client, Func action, CancellationToken cancellationToken = default) + static async Task TryWarmUp( + T client, Func action, CancellationToken cancellationToken = default + ) where T : KurrentDBClientBase { await Policy .Handle(ex => ex.StatusCode != StatusCode.Unimplemented) @@ -25,8 +26,7 @@ await Policy async ct => { try { await action(ct).ConfigureAwait(false); - } - catch (Exception ex) when (ex is not OperationCanceledException) { + } catch (Exception ex) when (ex is not OperationCanceledException) { // grpc throws a rpcexception when you cancel the token (which we convert into // invalid operation) - but polly expects operationcancelledexception or it wont // call onTimeoutAsync. so raise that here. @@ -40,7 +40,9 @@ await Policy return client; } - public static Task WarmUp(this KurrentDBClient dbClient, CancellationToken cancellationToken = default) => + public static Task WarmUp( + this KurrentDBClient dbClient, CancellationToken cancellationToken = default + ) => TryWarmUp( dbClient, async ct => { @@ -49,11 +51,11 @@ public static Task WarmUp(this KurrentDBClient dbClient, Cancel // 2. we are connected to leader if we require it var users = await dbClient .ReadStreamAsync( - direction: Direction.Forwards, - streamName: "$dbUsers", - revision: StreamPosition.Start, - maxCount: 1, - userCredentials: TestCredentials.Root, + "$dbUsers", + new ReadStreamOptions { + MaxCount = 1, + UserCredentials = TestCredentials.Root + }, cancellationToken: ct ) .ToArrayAsync(ct); @@ -63,17 +65,19 @@ public static Task WarmUp(this KurrentDBClient dbClient, Cancel // the read from leader above is not enough to guarantee the next write goes to leader _ = await dbClient.AppendToStreamAsync( - streamName: "warmup", - expectedState: StreamState.Any, - eventData: Enumerable.Empty(), - userCredentials: TestCredentials.Root, - cancellationToken: ct + "warmup", + StreamState.Any, + Enumerable.Empty(), + new AppendToStreamOptions { UserCredentials = TestCredentials.Root }, + ct ); }, cancellationToken ); - public static Task WarmUp(this KurrentDBOperationsClient client, CancellationToken cancellationToken = default) => + public static Task WarmUp( + this KurrentDBOperationsClient client, CancellationToken cancellationToken = default + ) => TryWarmUp( client, async ct => { @@ -81,7 +85,7 @@ await client.RestartPersistentSubscriptions( userCredentials: TestCredentials.Root, cancellationToken: ct ); - }, + }, cancellationToken ); @@ -119,7 +123,9 @@ public static Task WarmUp( cancellationToken ); - public static Task WarmUp(this KurrentDBUserManagementClient client, CancellationToken cancellationToken = default) => + public static Task WarmUp( + this KurrentDBUserManagementClient client, CancellationToken cancellationToken = default + ) => TryWarmUp( client, async ct => { diff --git a/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBPermanentFixture.Helpers.cs b/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBPermanentFixture.Helpers.cs index 2a9b80c05..9eb2d0293 100644 --- a/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBPermanentFixture.Helpers.cs +++ b/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBPermanentFixture.Helpers.cs @@ -32,7 +32,7 @@ public ReadOnlyMemory CreateMetadataOfSize(int metadataSize) => public ReadOnlyMemory CreateTestNonJsonMetadata() => "non-json-metadata"u8.ToArray(); - public (IEnumerable Events, uint size) CreateTestEventsUpToMaxSize(uint maxSize) { + public (IEnumerable Events, uint size) CreateTestEventsUpToMaxSize(uint maxSize) { var size = 0; var events = CreateTestEvents(int.MaxValue) @@ -42,7 +42,7 @@ public ReadOnlyMemory CreateMetadataOfSize(int metadataSize) => return (events, (uint)size); } - public IEnumerable CreateTestEvents( + public IEnumerable CreateTestEvents( int count = 1, string? type = null, ReadOnlyMemory? metadata = null, string? contentType = null ) => Enumerable.Range(0, count) @@ -53,12 +53,12 @@ public IEnumerable CreateTestMessages(int count = 1, object? metadata = Enumerable.Range(0, count) .Select(index => CreateTestMessage(index, metadata)); - public EventData CreateTestEvent( + public MessageData CreateTestEvent( string? type = null, ReadOnlyMemory? metadata = null, string? contentType = null ) => CreateTestEvent(0, type ?? TestEventType, metadata, contentType); - public IEnumerable CreateTestEventsThatThrowsException() { + public IEnumerable CreateTestEventsThatThrowsException() { // Ensure initial IEnumerator.Current does not throw yield return CreateTestEvent(1); @@ -66,7 +66,7 @@ public IEnumerable CreateTestEventsThatThrowsException() { throw new Exception(); } - protected static EventData CreateTestEvent(int index) => CreateTestEvent(index, TestEventType); + protected static MessageData CreateTestEvent(int index) => CreateTestEvent(index, TestEventType); protected static Message CreateTestMessage(int index, object? metadata = null) => Message.From( @@ -75,15 +75,14 @@ protected static Message CreateTestMessage(int index, object? metadata = null) = Uuid.NewUuid() ); - protected static EventData CreateTestEvent( + protected static MessageData CreateTestEvent( int index, string type, ReadOnlyMemory? metadata = null, string? contentType = null ) => new( - Uuid.NewUuid(), type, Encoding.UTF8.GetBytes($$"""{"x":{{index}}}"""), metadata, - contentType ?? "application/json" + contentType: contentType ?? "application/json" ); public async Task CreateTestUser(bool withoutGroups = true, bool useUserCredentials = false) { diff --git a/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBTemporaryFixture.Helpers.cs b/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBTemporaryFixture.Helpers.cs index ff491e4da..1fe14f9d5 100644 --- a/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBTemporaryFixture.Helpers.cs +++ b/test/KurrentDB.Client.Tests.Common/Fixtures/KurrentDBTemporaryFixture.Helpers.cs @@ -32,7 +32,7 @@ public ReadOnlyMemory CreateMetadataOfSize(int metadataSize) => public ReadOnlyMemory CreateTestNonJsonMetadata() => "non-json-metadata"u8.ToArray(); - public (IEnumerable Events, uint size) CreateTestEventsUpToMaxSize(uint maxSize) { + public (IEnumerable Events, uint size) CreateTestEventsUpToMaxSize(uint maxSize) { var size = 0; var events = CreateTestEvents(int.MaxValue) @@ -42,18 +42,18 @@ public ReadOnlyMemory CreateMetadataOfSize(int metadataSize) => return (events, (uint)size); } - public IEnumerable CreateTestEvents( + public IEnumerable CreateTestEvents( int count = 1, string? type = null, ReadOnlyMemory? metadata = null, string? contentType = null ) => Enumerable.Range(0, count) .Select(index => CreateTestEvent(index, type ?? TestEventType, metadata, contentType)); - public EventData CreateTestEvent( + public MessageData CreateTestEvent( string? type = null, ReadOnlyMemory? metadata = null, string? contentType = null ) => CreateTestEvent(0, type ?? TestEventType, metadata, contentType); - public IEnumerable CreateTestEventsThatThrowsException() { + public IEnumerable CreateTestEventsThatThrowsException() { // Ensure initial IEnumerator.Current does not throw yield return CreateTestEvent(1); @@ -61,17 +61,16 @@ public IEnumerable CreateTestEventsThatThrowsException() { throw new Exception(); } - protected static EventData CreateTestEvent(int index) => CreateTestEvent(index, TestEventType); + protected static MessageData CreateTestEvent(int index) => CreateTestEvent(index, TestEventType); - protected static EventData CreateTestEvent( + protected static MessageData CreateTestEvent( int index, string type, ReadOnlyMemory? metadata = null, string? contentType = null ) => new( - Uuid.NewUuid(), type, Encoding.UTF8.GetBytes($$"""{"x":{{index}}}"""), metadata, - contentType ?? "application/json" + contentType: contentType ?? "application/json" ); public async Task CreateTestUser(bool withoutGroups = true, bool useUserCredentials = false) { diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerExtensionsTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerExtensionsTests.cs deleted file mode 100644 index 122bf08f8..000000000 --- a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerExtensionsTests.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using KurrentDB.Client; -using KurrentDB.Client.Core.Serialization; - -namespace KurrentDB.Client.Tests.Core.Serialization; - -public class MessageSerializerExtensionsTests { - [Fact] - public void With_NullOperationSettings_ReturnsDefaultSerializer() { - // Given - var defaultSerializer = new DummyMessageSerializer(); - var defaultSettings = new KurrentDBClientSerializationSettings(); - - // When - var result = defaultSerializer.With(defaultSettings, null); - - // Then - Assert.Same(defaultSerializer, result); - } - - [Fact] - public void With_DisabledAutomaticDeserialization_ReturnsNullSerializer() { - // Given - var defaultSerializer = new DummyMessageSerializer(); - var defaultSettings = new KurrentDBClientSerializationSettings(); - var operationSettings = OperationSerializationSettings.Disabled; - - // When - var result = defaultSerializer.With(defaultSettings, operationSettings); - - // Then - Assert.Same(NullMessageSerializer.Instance, result); - } - - [Fact] - public void With_NoConfigureSettings_ReturnsDefaultSerializer() { - // Given - var defaultSerializer = new DummyMessageSerializer(); - var defaultSettings = new KurrentDBClientSerializationSettings(); - var operationSettings = new OperationSerializationSettings(); // Default-enabled with no config - - // When - var result = defaultSerializer.With(defaultSettings, operationSettings); - - // Then - Assert.Same(defaultSerializer, result); - } - - [Fact] - public void With_ConfigureSettings_CreatesNewMessageSerializer() { - // Given - var defaultSerializer = new DummyMessageSerializer(); - var defaultSettings = KurrentDBClientSerializationSettings.Default(); - - var operationSettings = OperationSerializationSettings.Configure( - s => - s.RegisterMessageType("CustomMessageName") - ); - - // When - var result = defaultSerializer.With(defaultSettings, operationSettings); - - // Then - Assert.NotSame(defaultSerializer, result); - Assert.IsType(result); - } - - [Fact] - public void Serialize_WithMultipleMessages_ReturnsArrayOfEventData() { - // Given - var serializer = new DummyMessageSerializer(); - var messages = new List { - Message.From(new object()), - Message.From(new object()), - Message.From(new object()) - }; - - var context = new MessageSerializationContext("test-stream", ContentType.Json); - - // When - var result = serializer.Serialize(messages, context); - - // Then - Assert.Equal(3, result.Length); - Assert.All(result, eventData => Assert.Equal("TestEvent", eventData.Type)); - } - - class DummyMessageSerializer : IMessageSerializer { - public EventData Serialize(Message value, MessageSerializationContext context) { - return new EventData( - Uuid.NewUuid(), - "TestEvent", - ReadOnlyMemory.Empty, - ReadOnlyMemory.Empty, - "application/json" - ); - } - -#if NET48 - public bool TryDeserialize(EventRecord record, out Message? deserialized) { -#else - public bool TryDeserialize(EventRecord record, [NotNullWhen(true)] out Message? deserialized) { -#endif - deserialized = null; - return false; - } - } - - public record UserRegistered(string UserId, string Email); -} diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs index f7165a4fe..b1b93fe73 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializerTests.cs @@ -1,28 +1,29 @@ -using KurrentDB.Client; using KurrentDB.Client.Core.Serialization; namespace KurrentDB.Client.Tests.Core.Serialization; +using static MessageTypeNamingResolutionContext; + public class MessageSerializerTests { [Fact] public void Serialize_WithValidMessage_ReturnsEventData() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var data = new UserRegistered("user-123", "user@random-email.com"); var metadata = new TestMetadata(); var messageId = Uuid.NewUuid(); var message = Message.From(data, metadata, messageId); - var context = new MessageSerializationContext("user-123", ContentType.Json); + var context = new MessageSerializationContext(FromStreamName("user-123")); // When var eventData = serializer.Serialize(message, context); // Then - Assert.Equal(messageId, eventData.EventId); + Assert.Equal(messageId, eventData.MessageId); Assert.Equal("UserRegistered", eventData.Type); Assert.NotEmpty(eventData.Data.Span.ToArray()); Assert.NotEmpty(eventData.Metadata.Span.ToArray()); @@ -34,18 +35,18 @@ public void Serialize_WithAutoGeneratedId_GeneratesNewId() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var data = new UserRegistered("user-123", "user@random-email.com"); var message = Message.From(data); // No ID provided - var context = new MessageSerializationContext("user-123", ContentType.Json); + var context = new MessageSerializationContext(FromStreamName("user-123")); // When var eventData = serializer.Serialize(message, context); // Then - Assert.NotEqual(Uuid.Empty, eventData.EventId); + Assert.NotEqual(Uuid.Empty, eventData.MessageId); } [Fact] @@ -53,12 +54,12 @@ public void Serialize_WithoutMetadata_GeneratesEmptyMetadata() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var data = new UserRegistered("user-123", "user@random-email.com"); var message = Message.From(data); - var context = new MessageSerializationContext("user-123", ContentType.Json); + var context = new MessageSerializationContext(FromStreamName("user-123")); // When var eventData = serializer.Serialize(message, context); @@ -70,14 +71,14 @@ public void Serialize_WithoutMetadata_GeneratesEmptyMetadata() { [Fact] public void Serialize_WithBinaryContentType_UsesBinarySerializer() { // Given - var settings = CreateTestSettings(); + var settings = CreateTestSettings().UseContentType(ContentType.Bytes); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var data = new UserRegistered("user-123", "user@random-email.com"); var message = Message.From(data); - var context = new MessageSerializationContext("user-123", ContentType.Bytes); + var context = new MessageSerializationContext(FromStreamName("user-123")); // When var eventData = serializer.Serialize(message, context); @@ -91,7 +92,7 @@ public void SerializeMultiple_WithValidMessages_ReturnsEventDataArray() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var messages = new[] { Message.From(new UserRegistered("1", "u1@random-email.com")), @@ -99,7 +100,7 @@ public void SerializeMultiple_WithValidMessages_ReturnsEventDataArray() { Message.From(new UserRegistered("3", "u3@random-email.com")) }; - var context = new MessageSerializationContext("user-123", ContentType.Json); + var context = new MessageSerializationContext(FromStreamName("user-123")); // When var eventDataArray = serializer.Serialize(messages, context); @@ -114,13 +115,13 @@ public void TryDeserialize_WithValidEventRecord_DeserializesSuccessfully() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var testEvent = new UserRegistered("user-123", "user@random-email.com"); var testMetadata = new TestMetadata { CorrelationId = "corr-123", UserId = "user-456" }; - var eventId = Uuid.NewUuid(); + var messageId = Uuid.NewUuid(); - var eventRecord = CreateTestEventRecord("UserRegistered", testEvent, testMetadata, eventId); + var eventRecord = CreateTestEventRecord("UserRegistered", testEvent, testMetadata, messageId); // When var success = serializer.TryDeserialize(eventRecord, out var message); @@ -128,7 +129,7 @@ public void TryDeserialize_WithValidEventRecord_DeserializesSuccessfully() { // Then Assert.True(success); Assert.NotNull(message); - Assert.Equal(eventId, message.MessageId); + Assert.Equal(messageId, message.MessageId); var deserializedEvent = Assert.IsType(message.Data); Assert.Equal("user-123", deserializedEvent.UserId); @@ -144,7 +145,7 @@ public void TryDeserialize_WithUnknownEventType_ReturnsFalse() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var testEvent = new UserRegistered("user-123", "user@random-email.com"); var eventRecord = CreateTestEventRecord("UnknownEventType", testEvent); @@ -162,7 +163,7 @@ public void TryDeserialize_WithNullData_ReturnsFalse() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var eventRecord = CreateTestEventRecord("UserRegistered", null); // When @@ -178,12 +179,12 @@ public void TryDeserialize_WithEmptyMetadata_DeserializesWithNullMetadata() { // Given var settings = CreateTestSettings(); var schemaRegistry = SchemaRegistry.From(settings); - var serializer = new MessageSerializer(schemaRegistry); + var serializer = new MessageSerializer(schemaRegistry, settings); var testEvent = new UserRegistered("user-123", "user@random-email.com"); - var eventId = Uuid.NewUuid(); + var messageId = Uuid.NewUuid(); - var eventRecord = CreateTestEventRecord("UserRegistered", testEvent, null, eventId); + var eventRecord = CreateTestEventRecord("UserRegistered", testEvent, null, messageId); // When var success = serializer.TryDeserialize(eventRecord, out var message); @@ -191,7 +192,7 @@ public void TryDeserialize_WithEmptyMetadata_DeserializesWithNullMetadata() { // Then Assert.True(success); Assert.NotNull(message); - Assert.Equal(eventId, message.MessageId); + Assert.Equal(messageId, message.MessageId); Assert.Null(message.Metadata); var deserializedEvent = Assert.IsType(message.Data); @@ -219,27 +220,109 @@ public void MessageSerializer_From_CreatesInstanceWithProvidedSettings() { // Then - test it works with our settings var data = new UserRegistered("user-123", "user@random-email.com"); var message = Message.From(data); - var context = new MessageSerializationContext("user-123", ContentType.Json); + + var context = new MessageSerializationContext(FromStreamName("user-123")); var eventData = serializer.Serialize(message, context); Assert.Equal("UserRegistered", eventData.Type); } + + [Fact] + public void With_NullOperationSettings_ReturnsDefaultMessageSerializer() { + // Given + var defaultSettings = new KurrentDBClientSerializationSettings(); + var messageSerializer = new MessageSerializer(SchemaRegistry.From(defaultSettings), defaultSettings); + + // When + var result = messageSerializer.With(null); + + // Then + Assert.Same(messageSerializer, result); + } + + [Fact] + public void With_DisabledAutomaticDeserialization_ReturnsNullSerializer() { + // Given + var defaultSettings = new KurrentDBClientSerializationSettings(); + var messageSerializer = new MessageSerializer(SchemaRegistry.From(defaultSettings), defaultSettings); + var operationSettings = OperationSerializationSettings.Disabled; + + // When + var result = messageSerializer.With(operationSettings); + + // Then + Assert.Same(NullMessageSerializer.Instance, result); + } + + [Fact] + public void With_NoConfigureSettings_ReturnsDefaultMessageSerializer() { + // Given + var defaultSettings = new KurrentDBClientSerializationSettings(); + var messageSerializer = new MessageSerializer(SchemaRegistry.From(defaultSettings), defaultSettings); + var operationSettings = new OperationSerializationSettings(); // Default-enabled with no config + + // When + var result = messageSerializer.With(operationSettings); + + // Then + Assert.Same(messageSerializer, result); + } + + [Fact] + public void With_ConfigureSettings_CreatesNewMessageSerializer() { + // Given + var defaultSettings = KurrentDBClientSerializationSettings.Get(); + var messageSerializer = new MessageSerializer(SchemaRegistry.From(defaultSettings), defaultSettings); + + var operationSettings = OperationSerializationSettings.Configure( + s => + s.MessageTypeMapping.Register("CustomMessageName") + ); + + // When + var result = messageSerializer.With(operationSettings); + + // Then + Assert.NotSame(messageSerializer, result); + Assert.IsType(result); + } + + [Fact] + public void Serialize_WithMultipleMessages_ReturnsArrayOfMessageData() { + // Given + var defaultSettings = KurrentDBClientSerializationSettings.Get(); + var messageSerializer = new MessageSerializer(SchemaRegistry.From(defaultSettings), defaultSettings); + var messages = new List { + Message.From(new UserRegistered("Joe Doe", "joe.doe@unknown.com")), + Message.From(new UserRegistered("Anne Smith", "anne.smith@unknown.com")), + Message.From(new UserRegistered("Ouro the Dragon", "ouro.dragon@unknown.com")) + }; + + var context = new MessageSerializationContext(FromStreamName("test-stream")); + + // When + var result = messageSerializer.Serialize(messages, context); + + // Then + Assert.Equal(3, result.Length); + } static KurrentDBClientSerializationSettings CreateTestSettings() { var settings = new KurrentDBClientSerializationSettings(); - settings.RegisterMessageType("UserRegistered"); - settings.RegisterMessageType("UserAssignedToRole"); - settings.UseMetadataType(); + settings.MessageTypeMapping + .Register("UserRegistered") + .Register("UserAssignedToRole") + .UseMetadataType(); return settings; } static EventRecord CreateTestEventRecord( - string eventType, object? data = null, object? metadata = null, Uuid? eventId = null + string eventType, object? data = null, object? metadata = null, Uuid? messageId = null ) => new( Uuid.NewUuid().ToString(), - eventId ?? Uuid.NewUuid(), + messageId ?? Uuid.NewUuid(), StreamPosition.FromInt64(0), new Position(1, 1), new Dictionary { @@ -248,11 +331,15 @@ static EventRecord CreateTestEventRecord( { Constants.Metadata.ContentType, Constants.Metadata.ContentTypes.ApplicationJson } }, data != null ? _serializer.Serialize(data) : ReadOnlyMemory.Empty, - metadata != null ? _serializer.Serialize(metadata) : ReadOnlyMemory.Empty + metadata != null ? _metadataSerializer.Serialize(metadata) : ReadOnlyMemory.Empty ); static readonly SystemTextJsonSerializer _serializer = new SystemTextJsonSerializer(); + static readonly SystemTextJsonSerializer _metadataSerializer = new SystemTextJsonSerializer( + new SystemTextJsonSerializationSettings { Options = KurrentDBClient.StreamMetadataJsonSerializerOptions } + ); + public record UserRegistered(string UserId, string Email); public record UserAssignedToRole(string UserId, string Role); diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializationContextTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeNamingResolutionContextTests.cs similarity index 71% rename from test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializationContextTests.cs rename to test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeNamingResolutionContextTests.cs index 46cf18ffc..4e8529d37 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/MessageSerializationContextTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeNamingResolutionContextTests.cs @@ -2,13 +2,13 @@ namespace KurrentDB.Client.Tests.Core.Serialization; -public class MessageSerializationContextTests +public class MessageTypeNamingResolutionContextTests { [Fact] public void CategoryName_ExtractsFromStreamName() { // Arrange - var context = new MessageSerializationContext("user-123", ContentType.Json); + var context = MessageTypeNamingResolutionContext.FromStreamName("user-123"); // Act var categoryName = context.CategoryName; @@ -21,7 +21,7 @@ public void CategoryName_ExtractsFromStreamName() public void CategoryName_ExtractsFromStreamNameWithMoreThanOneDash() { // Arrange - var context = new MessageSerializationContext("user-some-123", ContentType.Json); + var context = MessageTypeNamingResolutionContext.FromStreamName("user-some-123"); // Act var categoryName = context.CategoryName; @@ -34,7 +34,7 @@ public void CategoryName_ExtractsFromStreamNameWithMoreThanOneDash() public void CategoryName_ReturnsTheWholeStreamName() { // Arrange - var context = new MessageSerializationContext("user123", ContentType.Json); + var context = MessageTypeNamingResolutionContext.FromStreamName("user123"); // Act var categoryName = context.CategoryName; diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeRegistryTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeRegistryTests.cs index 4c92b4668..9dc9d6921 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeRegistryTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/MessageTypeRegistryTests.cs @@ -11,7 +11,7 @@ public void Register_StoresTypeAndTypeName() { const string typeName = "test-event-1"; // When - registry.Register(type, typeName); + registry.Register(typeName, type); // Then Assert.Equal(typeName, registry.GetTypeName(type)); @@ -19,7 +19,7 @@ public void Register_StoresTypeAndTypeName() { } [Fact] - public void Register_CalledTwiceForTheSameTypeOverridesExistingRegistration() { + public void Register_CalledTwiceForTheSameTypeOverridesExistingTypeRegistration() { // Given var registry = new MessageTypeRegistry(); var type = typeof(TestEvent1); @@ -27,8 +27,8 @@ public void Register_CalledTwiceForTheSameTypeOverridesExistingRegistration() { const string updatedTypeName = "updated-name"; // When - registry.Register(type, originalTypeName); - registry.Register(type, updatedTypeName); + registry.Register(originalTypeName, type); + registry.Register(updatedTypeName, type); // Then Assert.Equal(updatedTypeName, registry.GetTypeName(type)); @@ -69,7 +69,7 @@ public void GetOrAddTypeName_ReturnsExistingTypeName() { var type = typeof(TestEvent1); const string existingTypeName = "existing-type-name"; - registry.Register(type, existingTypeName); + registry.Register(existingTypeName, type); var typeResolutionCount = 0; // When @@ -116,7 +116,7 @@ public void GetOrAddClrType_ReturnsExistingClrType() { var registry = new MessageTypeRegistry(); var type = typeof(TestEvent1); const string typeName = "test-event-name"; - registry.Register(type, typeName); + registry.Register(typeName, type); var typeResolutionCount = 0; // When @@ -190,9 +190,9 @@ public void RegisterGeneric_RegistersTypeWithTypeName() { public void RegisterDictionary_RegistersMultipleTypes() { // Given var registry = new MessageTypeRegistry(); - var typeMap = new Dictionary { - { typeof(TestEvent1), "test-event-1" }, - { typeof(TestEvent2), "test-event-2" } + var typeMap = new Dictionary { + { "test-event-1", typeof(TestEvent1) }, + { "test-event-2", typeof(TestEvent2) } }; // When diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/NullMessageSerializerTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/NullMessageSerializerTests.cs index f1929e7ef..270c65d9c 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/NullMessageSerializerTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/NullMessageSerializerTests.cs @@ -1,15 +1,16 @@ using KurrentDB.Client.Core.Serialization; -using KurrentDB.Client; namespace KurrentDB.Client.Tests.Core.Serialization; +using static MessageTypeNamingResolutionContext; + public class NullMessageSerializerTests { [Fact] public void Serialize_ThrowsException() { // Given var serializer = NullMessageSerializer.Instance; var message = Message.From(new object()); - var context = new MessageSerializationContext("test-stream", ContentType.Json); + var context = new MessageSerializationContext(FromStreamName("test-stream")); // When & Assert Assert.Throws(() => serializer.Serialize(message, context)); diff --git a/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs b/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs index 4f78d7793..a9eaa663f 100644 --- a/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs +++ b/test/KurrentDB.Client.Tests/Core/Serialization/SchemaRegistryTests.cs @@ -1,7 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using KurrentDB.Client.Core.Serialization; -using KurrentDB.Client; namespace KurrentDB.Client.Tests.Core.Serialization; @@ -15,23 +14,6 @@ record TestEvent3; record TestMetadata; - [Fact] - public void Constructor_InitializesProperties() { - // Given - var serializers = new Dictionary { - { ContentType.Json, new SystemTextJsonSerializer() }, - { ContentType.Bytes, new SystemTextJsonSerializer() } - }; - - var namingStrategy = new DefaultMessageTypeNamingStrategy(typeof(TestMetadata)); - - // When - var registry = new SchemaRegistry(serializers, namingStrategy); - - // Then - Assert.Same(namingStrategy, registry.MessageTypeNamingStrategy); - } - [Fact] public void GetSerializer_ReturnsCorrectSerializer() { // Given @@ -45,7 +27,9 @@ public void GetSerializer_ReturnsCorrectSerializer() { var registry = new SchemaRegistry( serializers, - new DefaultMessageTypeNamingStrategy(typeof(TestMetadata)) + new DefaultMessageTypeNamingStrategy(typeof(TestMetadata)), + new MessageTypeRegistry(), + AutomaticTypeMappingRegistration.Enabled ); // When @@ -68,11 +52,9 @@ public void From_WithDefaultSettings_CreatesRegistryWithDefaults() { // Then Assert.NotNull(registry); - Assert.NotNull(registry.MessageTypeNamingStrategy); Assert.NotNull(registry.GetSerializer(ContentType.Json)); Assert.NotNull(registry.GetSerializer(ContentType.Bytes)); - Assert.IsType(registry.MessageTypeNamingStrategy); Assert.IsType(registry.GetSerializer(ContentType.Json)); Assert.IsType(registry.GetSerializer(ContentType.Bytes)); } @@ -121,60 +103,117 @@ public void From_WithCustomBytesSerializer_UsesProvidedSerializer() { public void From_WithMessageTypeMap_RegistersTypes() { // Given var settings = new KurrentDBClientSerializationSettings(); - settings.RegisterMessageType("test-event-1"); - settings.RegisterMessageType("test-event-2"); + settings.MessageTypeMapping.Register("test-event-1"); + settings.MessageTypeMapping.Register("test-event-2"); // When - var registry = SchemaRegistry.From(settings); - var namingStrategy = registry.MessageTypeNamingStrategy; + var registry = SchemaRegistry.From(settings); // Then // Verify types can be resolved - Assert.True(namingStrategy.TryResolveClrType("test-event-1", out var type1)); + Assert.True(registry.TryResolveClrType(TestRecord("test-event-1"), out var type1)); Assert.Equal(typeof(TestEvent1), type1); - Assert.True(namingStrategy.TryResolveClrType("test-event-2", out var type2)); + Assert.True(registry.TryResolveClrType(TestRecord("test-event-2"), out var type2)); Assert.Equal(typeof(TestEvent2), type2); } + [Fact] + public void From_WithCategoryMessageTypesMap_WithDefaultMessageAutoRegistration() { + // Given + var settings = new KurrentDBClientSerializationSettings(); + var defaultMessageTypeNamingStrategy = new DefaultMessageTypeNamingStrategy(settings.MessageTypeMapping.DefaultMetadataType); + + // When + var registry = SchemaRegistry.From(settings); + + // Then + // For categories, the naming strategy should have resolved the type names + // using the ResolveTypeName method, which by default uses the type's name + string typeName1 = registry.ResolveTypeName( + typeof(TestEvent1), + new MessageTypeNamingResolutionContext("category1") + ); + + string expectedTypeName1 = defaultMessageTypeNamingStrategy.ResolveTypeName( + typeof(TestEvent1), + new MessageTypeNamingResolutionContext("category1") + ); + + Assert.Equal(expectedTypeName1, typeName1); + + string typeName2 = registry.ResolveTypeName( + typeof(TestEvent2), + new MessageTypeNamingResolutionContext("category1") + ); + + string expectedTypeName2 = defaultMessageTypeNamingStrategy.ResolveTypeName( + typeof(TestEvent2), + new MessageTypeNamingResolutionContext("category1") + ); + + Assert.Equal(expectedTypeName2, typeName2); + + string typeName3 = registry.ResolveTypeName( + typeof(TestEvent3), + new MessageTypeNamingResolutionContext("category2") + ); + + string expectedTypeName3 = defaultMessageTypeNamingStrategy.ResolveTypeName( + typeof(TestEvent3), + new MessageTypeNamingResolutionContext("category2") + ); + + Assert.Equal(expectedTypeName3, typeName3); + + // Verify types can be resolved by the type names + Assert.True(registry.TryResolveClrType(TestRecord(typeName1), out var resolvedType1)); + Assert.Equal(typeof(TestEvent1), resolvedType1); + + Assert.True(registry.TryResolveClrType(TestRecord(typeName2), out var resolvedType2)); + Assert.Equal(typeof(TestEvent2), resolvedType2); + + Assert.True(registry.TryResolveClrType(TestRecord(typeName3), out var resolvedType3)); + Assert.Equal(typeof(TestEvent3), resolvedType3); + } + [Fact] public void From_WithCategoryMessageTypesMap_RegistersTypesWithCategories() { // Given var settings = new KurrentDBClientSerializationSettings(); - settings.RegisterMessageTypeForCategory("category1"); - settings.RegisterMessageTypeForCategory("category1"); - settings.RegisterMessageTypeForCategory("category2"); + settings.MessageTypeMapping.RegisterForCategory("category1"); + settings.MessageTypeMapping.RegisterForCategory("category1"); + settings.MessageTypeMapping.RegisterForCategory("category2"); // When - var registry = SchemaRegistry.From(settings); - var namingStrategy = registry.MessageTypeNamingStrategy; + var registry = SchemaRegistry.From(settings); // Then // For categories, the naming strategy should have resolved the type names // using the ResolveTypeName method, which by default uses the type's name - string typeName1 = namingStrategy.ResolveTypeName( + string typeName1 = registry.ResolveTypeName( typeof(TestEvent1), new MessageTypeNamingResolutionContext("category1") ); - string typeName2 = namingStrategy.ResolveTypeName( + string typeName2 = registry.ResolveTypeName( typeof(TestEvent2), new MessageTypeNamingResolutionContext("category1") ); - string typeName3 = namingStrategy.ResolveTypeName( + string typeName3 = registry.ResolveTypeName( typeof(TestEvent3), new MessageTypeNamingResolutionContext("category2") ); // Verify types can be resolved by the type names - Assert.True(namingStrategy.TryResolveClrType(typeName1, out var resolvedType1)); + Assert.True(registry.TryResolveClrType(TestRecord(typeName1), out var resolvedType1)); Assert.Equal(typeof(TestEvent1), resolvedType1); - Assert.True(namingStrategy.TryResolveClrType(typeName2, out var resolvedType2)); + Assert.True(registry.TryResolveClrType(TestRecord(typeName2), out var resolvedType2)); Assert.Equal(typeof(TestEvent2), resolvedType2); - Assert.True(namingStrategy.TryResolveClrType(typeName3, out var resolvedType3)); + Assert.True(registry.TryResolveClrType(TestRecord(typeName3), out var resolvedType3)); Assert.Equal(typeof(TestEvent3), resolvedType3); } @@ -190,11 +229,8 @@ public void From_WithCustomNamingStrategy_UsesProvidedStrategy() { // Then // The registry wraps the naming strategy, but should still use it - var wrappedStrategy = registry.MessageTypeNamingStrategy; - Assert.IsType(wrappedStrategy); - // Test to make sure it behaves like our custom strategy - string typeName = wrappedStrategy.ResolveTypeName( + string typeName = registry.ResolveTypeName( typeof(TestEvent1), new MessageTypeNamingResolutionContext("test") ); @@ -208,22 +244,34 @@ public void From_WithNoMessageTypeNamingStrategy_UsesDefaultStrategy() { // Given var settings = new KurrentDBClientSerializationSettings { MessageTypeNamingStrategy = null, - DefaultMetadataType = typeof(TestMetadata) - }; + }.ConfigureTypeMap(setting => setting.DefaultMetadataType = typeof(TestMetadata)); // When var registry = SchemaRegistry.From(settings); // Then - Assert.NotNull(registry.MessageTypeNamingStrategy); - // The wrapped default strategy should use our metadata type - Assert.True( - registry.MessageTypeNamingStrategy.TryResolveClrMetadataType("some-type", out var defaultMetadataType) - ); + Assert.True(registry.TryResolveClrMetadataType(TestRecord("some-type"), out var defaultMetadataType)); Assert.Equal(typeof(TestMetadata), defaultMetadataType); } + + static EventRecord TestRecord( + string eventType + ) => + new( + Uuid.NewUuid().ToString(), + Uuid.NewUuid(), + StreamPosition.FromInt64(0), + new Position(1, 1), + new Dictionary { + { Constants.Metadata.Type, eventType }, + { Constants.Metadata.Created, DateTime.UtcNow.ToTicksSinceEpoch().ToString() }, + { Constants.Metadata.ContentType, Constants.Metadata.ContentTypes.ApplicationJson } + }, + ReadOnlyMemory.Empty, + ReadOnlyMemory.Empty + ); // Custom naming strategy for testing class TestNamingStrategy : IMessageTypeNamingStrategy { @@ -231,26 +279,26 @@ public string ResolveTypeName(Type type, MessageTypeNamingResolutionContext cont return $"Custom-{type.Name}-{context.CategoryName}"; } #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? clrType) + public bool TryResolveClrTypeName(EventRecord record, out string? clrType) #else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? clrType) + public bool TryResolveClrTypeName(EventRecord record, [NotNullWhen(true)] out string? clrType) #endif { // Simple implementation for testing - clrType = messageTypeName.StartsWith("Custom-TestEvent1") - ? typeof(TestEvent1) + clrType = record.EventType.StartsWith("Custom-TestEvent1") + ? typeof(TestEvent1).FullName : null; return clrType != null; } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? clrType) + public bool TryResolveClrMetadataTypeName(EventRecord record, out string? clrType) #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? clrType) + public bool TryResolveClrMetadataTypeName(EventRecord record, [NotNullWhen(true)] out string? clrType) #endif { - clrType = typeof(TestMetadata); + clrType = typeof(TestMetadata).FullName!; return true; } } diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/FilterTestCases.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/FilterTestCases.cs index 66d4562d5..4919009ab 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/FilterTestCases.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/FilterTestCases.cs @@ -9,23 +9,23 @@ public static class Filters { const string EventTypePrefix = nameof(EventTypePrefix); const string EventTypeRegex = nameof(EventTypeRegex); - static readonly IDictionary, Func)> + static readonly IDictionary, Func)> s_filters = - new Dictionary, Func)> { + new Dictionary, Func)> { [StreamNamePrefix] = (StreamFilter.Prefix, (_, e) => e), [StreamNameRegex] = (f => StreamFilter.RegularExpression(f), (_, e) => e), [EventTypePrefix] = (EventTypeFilter.Prefix, (term, e) => new( - e.EventId, term, e.Data, e.Metadata, + e.MessageId, e.ContentType )), [EventTypeRegex] = (f => EventTypeFilter.RegularExpression(f), (term, e) => new( - e.EventId, term, e.Data, e.Metadata, + e.MessageId, e.ContentType )) }; @@ -35,7 +35,7 @@ public static class Filters { .Where(fi => fi.IsLiteral && !fi.IsInitOnly) .Select(fi => (string)fi.GetRawConstantValue()!); - public static (Func getFilter, Func prepareEvent) + public static (Func getFilter, Func prepareEvent) GetFilter(string name) => s_filters[name]; } diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs index dac2dde80..b0f404070 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs @@ -1,5 +1,4 @@ using KurrentDB.Client.Tests.TestNode; -using KurrentDB.Client; namespace KurrentDB.Client.Tests.PersistentSubscriptions; @@ -30,7 +29,8 @@ await Fixture.Subscriptions.CreateToAllAsync( using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (subscription, e, r, ct) => { + new PersistentSubscriptionListener{ + EventAppeared = async (subscription, e, r, ct) => { if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { await subscription.Ack(e); return; @@ -39,11 +39,11 @@ await Fixture.Subscriptions.CreateToAllAsync( firstNonSystemEventSource.TrySetResult(e); await subscription.Ack(e); }, - (subscription, reason, ex) => { + SubscriptionDropped = (subscription, reason, ex) => { if (reason != SubscriptionDroppedReason.Disposed) firstNonSystemEventSource.TrySetException(ex!); - }, - TestCredentials.Root + }}, + new SubscribeToPersistentSubscriptionOptions{ UserCredentials = TestCredentials.Root } ); await Assert.ThrowsAsync(() => firstNonSystemEventSource.Task.WithTimeout()); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs index 04794ea81..58b8b1379 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs @@ -4,7 +4,10 @@ namespace KurrentDB.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests(ITestOutputHelper output, KurrentDBTemporaryFixture fixture) +public class SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests( + ITestOutputHelper output, + KurrentDBTemporaryFixture fixture +) : KurrentTemporaryTests(output, fixture) { [RetryFact] public async Task connect_to_existing_with_start_from_set_to_end_position() { @@ -21,24 +24,30 @@ await Fixture.Streams.AppendToStreamAsync( ); } - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.End), + userCredentials: TestCredentials.Root + ); using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (subscription, e, r, ct) => { - if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + await subscription.Ack(e); + return; + } + + firstNonSystemEventSource.TrySetResult(e); await subscription.Ack(e); - return; + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstNonSystemEventSource.TrySetException(ex!); } - - firstNonSystemEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstNonSystemEventSource.TrySetException(ex!); }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Assert.ThrowsAsync(() => firstNonSystemEventSource.Task.WithTimeout()); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs index 0333b6093..1904e830b 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs @@ -26,7 +26,7 @@ await Assert.ThrowsAsync( using var _ = await Fixture.Subscriptions.SubscribeToAllAsync( group, delegate { return Task.CompletedTask; }, - userCredentials: user + new SubscribeToPersistentSubscriptionOptions { UserCredentials = user } ); } ); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs index 9cd08e4b9..74d3aa60a 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllFilterObsoleteTests.cs @@ -1,5 +1,4 @@ using KurrentDB.Client.Tests.TestNode; -using KurrentDB.Client; namespace KurrentDB.Client.Tests.PersistentSubscriptions; @@ -24,7 +23,7 @@ await Fixture.Streams.SetStreamMetadataAsync( SystemStreams.AllStream, StreamState.Any, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); var appearedEvents = new List(); @@ -45,7 +44,10 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await subscription.Messages .OfType() @@ -59,7 +61,7 @@ await subscription.Messages ) .WithTimeout(); - Assert.Equal(events.Select(x => x.EventId), appearedEvents.Select(x => x.EventId)); + Assert.Equal(events.Select(x => x.MessageId), appearedEvents.Select(x => x.EventId)); } [RetryTheory] @@ -86,14 +88,14 @@ await Fixture.Streams.SetStreamMetadataAsync( SystemStreams.AllStream, StreamState.Any, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); foreach (var e in eventsToSkip) { await Fixture.Streams.AppendToStreamAsync( $"{streamPrefix}_{Guid.NewGuid():n}", StreamState.NoStream, - new[] { e } + [e] ); } @@ -101,7 +103,7 @@ await Fixture.Streams.AppendToStreamAsync( var result = await Fixture.Streams.AppendToStreamAsync( $"{streamPrefix}_{Guid.NewGuid():n}", StreamState.NoStream, - new[] { e } + [e] ); eventToCaptureResult ??= result; @@ -114,7 +116,11 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = + Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var appearedEvents = await subscription.Messages.OfType() .Take(10) @@ -123,7 +129,7 @@ await Fixture.Subscriptions.CreateToAllAsync( .AsTask() .WithTimeout(); - Assert.Equal(eventsToCapture.Select(x => x.EventId), appearedEvents.Select(x => x.EventId)); + Assert.Equal(eventsToCapture.Select(x => x.MessageId), appearedEvents.Select(x => x.EventId)); } public static IEnumerable FilterCases() => Filters.All.Select(filter => new object[] { filter }); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllGetInfoObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllGetInfoObsoleteTests.cs index 1e1769d48..9777e5db3 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllGetInfoObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllGetInfoObsoleteTests.cs @@ -89,7 +89,7 @@ await Subscriptions.SubscribeToAllAsync( return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); }; } diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllNoDefaultCredentialsObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllNoDefaultCredentialsObsoleteTests.cs index f0bfb0a5e..9ba4201a7 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllNoDefaultCredentialsObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllNoDefaultCredentialsObsoleteTests.cs @@ -30,7 +30,7 @@ public async Task throws_persistent_subscription_not_found() { using var _ = await Fixture.Subscriptions.SubscribeToAllAsync( group, delegate { return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); } ).WithTimeout(); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs index 2542dc113..d0d483a8f 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllObsoleteTests.cs @@ -10,12 +10,16 @@ public async Task connect_to_existing_with_max_one_client() { // Arrange var group = Fixture.GetGroupName(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(maxSubscriberCount: 1), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(maxSubscriberCount: 1), + userCredentials: TestCredentials.Root + ); using var first = await Fixture.Subscriptions.SubscribeToAllAsync( group, delegate { return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ).WithTimeout(); var ex = await Assert.ThrowsAsync( @@ -23,7 +27,7 @@ public async Task connect_to_existing_with_max_one_client() { using var _ = await Fixture.Subscriptions.SubscribeToAllAsync( group, delegate { return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); } ).WithTimeout(); @@ -41,9 +45,11 @@ public async Task connect_to_existing_with_permissions() { var dropped = new TaskCompletionSource<(SubscriptionDroppedReason, Exception?)>(); using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - delegate { return Task.CompletedTask; }, - (s, reason, ex) => dropped.TrySetResult((reason, ex)), - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = delegate { return Task.CompletedTask; }, + SubscriptionDropped = (s, reason, ex) => dropped.TrySetResult((reason, ex)), + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ).WithTimeout(); Assert.NotNull(subscription); @@ -67,22 +73,28 @@ await Fixture.Streams.AppendToStreamAsync( } var events = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, 10, userCredentials: TestCredentials.Root) + .ReadAllAsync(new ReadAllOptions { MaxCount = 10, UserCredentials = TestCredentials.Root }) .ToArrayAsync(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var resolvedEvent = await firstEventSource.Task.WithTimeout(TimeSpan.FromSeconds(10)); @@ -110,26 +122,28 @@ await Fixture.Streams.AppendToStreamAsync( await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (subscription, e, r, ct) => { - if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + await subscription.Ack(e); + return; + } + + firstNonSystemEventSource.TrySetResult(e); await subscription.Ack(e); - return; + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstNonSystemEventSource.TrySetException(ex!); } - - firstNonSystemEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstNonSystemEventSource.TrySetException(ex!); }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); var resolvedEvent = await firstNonSystemEventSource.Task.WithTimeout(); - Assert.Equal(expectedEvent!.EventId, resolvedEvent.Event.EventId); + Assert.Equal(expectedEvent.MessageId, resolvedEvent.Event.EventId); Assert.Equal(expectedStreamId, resolvedEvent.Event.EventStreamId); } @@ -150,29 +164,36 @@ await Fixture.Streams.AppendToStreamAsync( ); } - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.End), + userCredentials: TestCredentials.Root + ); + using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (subscription, e, r, ct) => { - if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + if (SystemStreams.IsSystemStream(e.OriginalStreamId)) { + await subscription.Ack(e); + return; + } + + firstNonSystemEventSource.TrySetResult(e); await subscription.Ack(e); - return; + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstNonSystemEventSource.TrySetException(ex!); } - - firstNonSystemEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstNonSystemEventSource.TrySetException(ex!); }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); var resolvedEvent = await firstNonSystemEventSource.Task.WithTimeout(); - Assert.Equal(expectedEvent.EventId, resolvedEvent.Event.EventId); + Assert.Equal(expectedEvent.MessageId, resolvedEvent.Event.EventId); Assert.Equal(expectedStreamId, resolvedEvent.Event.EventStreamId); } @@ -191,9 +212,11 @@ await Fixture.Subscriptions.CreateToAllAsync( using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (subscription, e, r, ct) => await subscription.Ack(e), - (subscription, reason, ex) => { dropped.TrySetResult((reason, ex)); }, - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => await subscription.Ack(e), + SubscriptionDropped = (subscription, reason, ex) => { dropped.TrySetResult((reason, ex)); } + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var (reason, exception) = await dropped.Task.WithTimeout(); @@ -208,7 +231,7 @@ public async Task connect_to_existing_with_start_from_set_to_valid_middle_positi TaskCompletionSource firstEventSource = new(); var events = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, 10, userCredentials: TestCredentials.Root) + .ReadAllAsync(new ReadAllOptions { MaxCount = 10, UserCredentials = TestCredentials.Root }) .ToArrayAsync(); var expectedEvent = events[events.Length / 2]; //just a random event in the middle of the results @@ -221,15 +244,17 @@ await Fixture.Subscriptions.CreateToAllAsync( using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var resolvedEvent = await firstEventSource.Task.WithTimeout(); @@ -245,28 +270,34 @@ public async Task connect_with_retries() { TaskCompletionSource retryCountSource = new(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); // Act using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (subscription, e, r, ct) => { - if (r > 4) { - retryCountSource.TrySetResult(r.Value); - await subscription.Ack(e.Event.EventId); - } else { - await subscription.Nack( - PersistentSubscriptionNakEventAction.Retry, - "Not yet tried enough times", - e - ); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + if (r > 4) { + retryCountSource.TrySetResult(r.Value); + await subscription.Ack(e.Event.EventId); + } else { + await subscription.Nack( + PersistentSubscriptionNakEventAction.Retry, + "Not yet tried enough times", + e + ); + } + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + retryCountSource.TrySetException(ex!); } }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - retryCountSource.TrySetException(ex!); - }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); // Assert @@ -283,9 +314,11 @@ public async Task deleting_existing_with_subscriber() { using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (s, e, i, ct) => await s.Ack(e), - (s, r, e) => dropped.TrySetResult((r, e)), - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = async (s, e, i, ct) => await s.Ack(e), + SubscriptionDropped = (s, r, e) => dropped.TrySetResult((r, e)), + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); // todo: investigate why this test is flaky without this delay @@ -410,18 +443,22 @@ await Fixture.Subscriptions.CreateToAllAsync( using var subscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (subscription, e, retryCount, ct) => { - await subscription.Ack(e); - if (e.OriginalStreamId.StartsWith("test-") - && Interlocked.Increment(ref eventReceivedCount) == events.Length) - eventsReceived.TrySetResult(true); - }, - (s, r, e) => { - if (e != null) - eventsReceived.TrySetException(e); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, retryCount, ct) => { + await subscription.Ack(e); + if (e.OriginalStreamId.StartsWith("test-") + && Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + SubscriptionDropped = (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + } }, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); foreach (var e in events) { @@ -453,16 +490,18 @@ await Fixture.Subscriptions.CreateToAllAsync( using var firstSubscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (s, e, r, ct) => { - appearedEvents.Add(e); + new PersistentSubscriptionListener { + EventAppeared = async (s, e, r, ct) => { + appearedEvents.Add(e); - if (appearedEvents.Count == events.Length) - appeared.TrySetResult(true); + if (appearedEvents.Count == events.Length) + appeared.TrySetResult(true); - await s.Ack(e); + await s.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), }, - (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Task.WhenAll(appeared.Task, WaitForCheckpoint().WithTimeout()); @@ -474,18 +513,20 @@ await Fixture.Subscriptions.CreateToAllAsync( using var secondSubscription = await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (s, e, r, ct) => { - resumedSource.TrySetResult(e); - await s.Ack(e); - s.Dispose(); - }, - (_, reason, ex) => { - if (ex is not null) - resumedSource.TrySetException(ex); - else - resumedSource.TrySetResult(default); + new PersistentSubscriptionListener { + EventAppeared = async (s, e, r, ct) => { + resumedSource.TrySetResult(e); + await s.Ack(e); + s.Dispose(); + }, + SubscriptionDropped = (_, reason, ex) => { + if (ex is not null) + resumedSource.TrySetException(ex); + else + resumedSource.TrySetResult(default); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); foreach (var e in events) @@ -499,8 +540,7 @@ await Fixture.Subscriptions.CreateToAllAsync( async Task WaitForCheckpoint() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -524,7 +564,7 @@ public async Task update_existing_with_check_point_filtered() { List appearedEvents = []; - EventData[] events = Fixture.CreateTestEvents(5).ToArray(); + MessageData[] events = Fixture.CreateTestEvents(5).ToArray(); Position checkPoint = default; @@ -544,16 +584,18 @@ await Fixture.Subscriptions.CreateToAllAsync( await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (s, e, r, ct) => { - appearedEvents.Add(e); + new PersistentSubscriptionListener { + EventAppeared = async (s, e, r, ct) => { + appearedEvents.Add(e); - if (appearedEvents.Count == events.Length) - appeared.TrySetResult(true); + if (appearedEvents.Count == events.Length) + appeared.TrySetResult(true); - await s.Ack(e); + await s.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), }, - (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Task.WhenAll(appeared.Task, Checkpointed()).WithTimeout(); @@ -565,18 +607,20 @@ await Fixture.Subscriptions.SubscribeToAllAsync( await Fixture.Subscriptions.SubscribeToAllAsync( group, - async (s, e, r, ct) => { - resumedSource.TrySetResult(e); - await s.Ack(e); - s.Dispose(); + new PersistentSubscriptionListener { + EventAppeared = async (s, e, r, ct) => { + resumedSource.TrySetResult(e); + await s.Ack(e); + s.Dispose(); + }, + SubscriptionDropped = (_, reason, ex) => { + if (ex is not null) + resumedSource.TrySetException(ex); + else + resumedSource.TrySetResult(default); + }, }, - (_, reason, ex) => { - if (ex is not null) - resumedSource.TrySetException(ex); - else - resumedSource.TrySetResult(default); - }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); foreach (var e in events) @@ -592,8 +636,7 @@ await Fixture.Subscriptions.SubscribeToAllAsync( async Task Checkpointed() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -613,13 +656,19 @@ public async Task update_existing_with_subscribers() { TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> droppedSource = new(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); using var subscription = Fixture.Subscriptions.SubscribeToAllAsync( group, - delegate { return Task.CompletedTask; }, - (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = delegate { return Task.CompletedTask; }, + SubscriptionDropped = (subscription, reason, ex) => droppedSource.TrySetResult((reason, ex)), + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); // todo: investigate why this test is flaky without this delay @@ -676,7 +725,7 @@ await Fixture.Subscriptions.CreateToAllAsync( await s.Ack(e); }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Task.WhenAll(appeared.Task, WaitForCheckpoints().WithTimeout()); @@ -690,7 +739,7 @@ await Fixture.Streams.AppendToStreamAsync( } Assert.True(secondCheckPoint > firstCheckPoint); - Assert.Equal(events.Select(e => e.EventId), appearedEvents.Select(e => e.Event.EventId)); + Assert.Equal(events.Select(e => e.MessageId), appearedEvents.Select(e => e.Event.EventId)); return; @@ -698,8 +747,7 @@ async Task WaitForCheckpoints() { bool firstCheckpointSet = false; await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs index 24ad7a96a..e59a772fe 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/Obsolete/SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs @@ -5,7 +5,10 @@ namespace KurrentDB.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToAllUpdateExistingWithCheckpointObsoleteTests(ITestOutputHelper output, KurrentDBTemporaryFixture fixture) +public class SubscribeToAllUpdateExistingWithCheckpointObsoleteTests( + ITestOutputHelper output, + KurrentDBTemporaryFixture fixture +) : KurrentTemporaryTests(output, fixture) { [RetryFact] public async Task update_existing_with_check_point_should_resumes_from_check_point() { @@ -25,7 +28,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); @@ -38,7 +45,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents(1)); - await using var sub = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var sub = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var resolvedEvent = await sub.Messages .OfType() @@ -71,8 +82,7 @@ async Task Subscribe() { async Task WaitForCheckpoint() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-{stream}::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs index 1ba566fb8..27165f482 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs @@ -4,7 +4,10 @@ namespace KurrentDB.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToAllConnectToExistingWithStartFromNotSetTests(ITestOutputHelper output, KurrentDBTemporaryFixture fixture) +public class SubscribeToAllConnectToExistingWithStartFromNotSetTests( + ITestOutputHelper output, + KurrentDBTemporaryFixture fixture +) : KurrentTemporaryTests(output, fixture) { [RetryFact] public async Task connect_to_existing_with_start_from_not_set() { @@ -19,7 +22,10 @@ await Fixture.Streams.AppendToStreamAsync( ); await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await Assert.ThrowsAsync( () => subscription.Messages diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs index e85a1ff1e..c1c7fa49d 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs @@ -1,10 +1,12 @@ -using KurrentDB.Client; using KurrentDB.Client.Tests.TestNode; namespace KurrentDB.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests(ITestOutputHelper output, KurrentDBTemporaryFixture fixture) +public class SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests( + ITestOutputHelper output, + KurrentDBTemporaryFixture fixture +) : KurrentTemporaryTests(output, fixture) { [RetryFact] public async Task connect_to_existing_with_start_from_set_to_end_position() { @@ -19,9 +21,16 @@ await Fixture.Streams.AppendToStreamAsync( ); } - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.End), + userCredentials: TestCredentials.Root + ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await Assert.ThrowsAsync( () => subscription.Messages diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectWithoutReadPermissionsTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectWithoutReadPermissionsTests.cs index b12fb8646..1ec4aa4df 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectWithoutReadPermissionsTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllConnectWithoutReadPermissionsTests.cs @@ -1,10 +1,12 @@ -using KurrentDB.Client; using KurrentDB.Client.Tests.TestNode; namespace KurrentDB.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToAllConnectWithoutReadPermissionsTests(ITestOutputHelper output, KurrentDBTemporaryFixture fixture) +public class SubscribeToAllConnectWithoutReadPermissionsTests( + ITestOutputHelper output, + KurrentDBTemporaryFixture fixture +) : KurrentTemporaryTests(output, fixture) { [RetryFact] public async Task connect_to_existing_without_read_all_permissions() { @@ -23,7 +25,11 @@ await Fixture.DbUsers.CreateUserWithRetry( await Assert.ThrowsAsync( async () => { - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: user); + await using var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = user } + ); + await subscription.Messages.AnyAsync().AsTask().WithTimeout(); } ); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs index 6ce230a56..7673f58b3 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllFilterTests.cs @@ -25,7 +25,7 @@ await Fixture.Streams.SetStreamMetadataAsync( SystemStreams.AllStream, StreamState.Any, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); var appearedEvents = new List(); @@ -46,7 +46,10 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await subscription.Messages .OfType() @@ -60,7 +63,7 @@ await subscription.Messages ) .WithTimeout(); - Assert.Equal(events.Select(x => x.EventId), appearedEvents.Select(x => x.EventId)); + Assert.Equal(events.Select(x => x.MessageId), appearedEvents.Select(x => x.EventId)); } [RetryTheory] @@ -87,7 +90,7 @@ await Fixture.Streams.SetStreamMetadataAsync( SystemStreams.AllStream, StreamState.Any, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); foreach (var e in eventsToSkip) { @@ -115,7 +118,8 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = + Fixture.Subscriptions.SubscribeToAll(group, new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root }); var appearedEvents = await subscription.Messages.OfType() .Take(10) @@ -124,7 +128,7 @@ await Fixture.Subscriptions.CreateToAllAsync( .AsTask() .WithTimeout(); - Assert.Equal(eventsToCapture.Select(x => x.EventId), appearedEvents.Select(x => x.EventId)); + Assert.Equal(eventsToCapture.Select(x => x.MessageId), appearedEvents.Select(x => x.EventId)); } public static IEnumerable FilterCases() => Filters.All.Select(filter => new object[] { filter }); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs index ca66c5e9d..166583611 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllGetInfoTests.cs @@ -55,7 +55,10 @@ await fixture.Subscriptions.GetInfoToAllAsync(group, userCredentials: TestCreden [RetryFact] public async Task returns_result_with_normal_user_credentials() { - var result = await fixture.Subscriptions.GetInfoToAllAsync(fixture.Group, userCredentials: TestCredentials.Root); + var result = await fixture.Subscriptions.GetInfoToAllAsync( + fixture.Group, + userCredentials: TestCredentials.Root + ); Assert.Equal("$all", result.EventSource); } @@ -74,13 +77,16 @@ await Streams.AppendToStreamAsync( $"test-{Guid.NewGuid():n}", StreamState.NoStream, [eventData], - userCredentials: TestCredentials.Root + new AppendToStreamOptions { UserCredentials = TestCredentials.Root } ); } var counter = 0; - await using var subscription = Subscriptions.SubscribeToAll(Group, userCredentials: TestCredentials.Root); + await using var subscription = Subscriptions.SubscribeToAll( + Group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var enumerator = subscription.Messages.GetAsyncEnumerator(); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllNoDefaultCredentialsTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllNoDefaultCredentialsTests.cs index 314d71887..34d575db1 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllNoDefaultCredentialsTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllNoDefaultCredentialsTests.cs @@ -1,9 +1,10 @@ -using KurrentDB.Client; - namespace KurrentDB.Client.Tests.PersistentSubscriptions; [Trait("Category", "Target:PersistentSubscriptions")] -public class SubscribeToAllNoDefaultCredentialsTests(ITestOutputHelper output, SubscribeToAllNoDefaultCredentialsTests.CustomFixture fixture) +public class SubscribeToAllNoDefaultCredentialsTests( + ITestOutputHelper output, + SubscribeToAllNoDefaultCredentialsTests.CustomFixture fixture +) : KurrentPermanentTests(output, fixture) { [RetryFact] public async Task connect_to_existing_without_permissions() { @@ -23,7 +24,10 @@ await Assert.ThrowsAsync( public async Task throws_persistent_subscription_not_found() { var group = Fixture.GetGroupName(); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); Assert.True( await subscription.Messages.OfType().AnyAsync() @@ -34,7 +38,9 @@ public async Task throws_persistent_subscription_not_found() { [RetryFact] public async Task deleting_without_permissions() { - await Assert.ThrowsAsync(() => Fixture.Subscriptions.DeleteToAllAsync(Guid.NewGuid().ToString())); + await Assert.ThrowsAsync( + () => Fixture.Subscriptions.DeleteToAllAsync(Guid.NewGuid().ToString()) + ); } [RetryFact] diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs index 97c9b3e3e..c5411ad9c 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllTests.cs @@ -28,16 +28,27 @@ public async Task connect_to_existing_with_max_one_client() { // Arrange var group = Fixture.GetGroupName(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(maxSubscriberCount: 1), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(maxSubscriberCount: 1), + userCredentials: TestCredentials.Root + ); - var ex = await Assert.ThrowsAsync(() => Task.WhenAll(Subscribe().WithTimeout(), Subscribe().WithTimeout())); + var ex = await Assert.ThrowsAsync( + () => Task.WhenAll(Subscribe().WithTimeout(), Subscribe().WithTimeout()) + ); Assert.Equal(SystemStreams.AllStream, ex.StreamName); Assert.Equal(group, ex.GroupName); return; async Task Subscribe() { - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = + Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + await subscription.Messages.AnyAsync(); } } @@ -49,7 +60,11 @@ public async Task connect_to_existing_with_permissions() { await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); // Act - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = + Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); // Assert subscription.Messages @@ -72,12 +87,19 @@ await Fixture.Streams.AppendToStreamAsync( } var events = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, 10, userCredentials: TestCredentials.Root) + .ReadAllAsync(new ReadAllOptions { MaxCount = 10, UserCredentials = TestCredentials.Root }) .ToArrayAsync(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var resolvedEvent = await subscription.Messages.OfType() .Select(e => e.ResolvedEvent) @@ -103,7 +125,10 @@ await Fixture.Streams.AppendToStreamAsync( } await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); @@ -112,7 +137,7 @@ await Fixture.Streams.AppendToStreamAsync( .Where(resolvedEvent => !SystemStreams.IsSystemStream(resolvedEvent.OriginalStreamId)) .FirstOrDefaultAsync().AsTask().WithTimeout(); - Assert.Equal(expectedEvent.EventId, resolvedEvent.Event.EventId); + Assert.Equal(expectedEvent.MessageId, resolvedEvent.Event.EventId); Assert.Equal(expectedStreamId, resolvedEvent.Event.EventStreamId); } @@ -131,8 +156,16 @@ await Fixture.Streams.AppendToStreamAsync( ); } - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.End), userCredentials: TestCredentials.Root); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.End), + userCredentials: TestCredentials.Root + ); + + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await Fixture.Streams.AppendToStreamAsync(expectedStreamId, StreamState.NoStream, [expectedEvent]); @@ -144,7 +177,7 @@ await Fixture.Streams.AppendToStreamAsync( .AsTask() .WithTimeout(); - Assert.Equal(expectedEvent.EventId, resolvedEvent.Event.EventId); + Assert.Equal(expectedEvent.MessageId, resolvedEvent.Event.EventId); Assert.Equal(expectedStreamId, resolvedEvent.Event.EventStreamId); } @@ -159,8 +192,12 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); - var enumerator = subscription.Messages.GetAsyncEnumerator(); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + + var enumerator = subscription.Messages.GetAsyncEnumerator(); await enumerator.MoveNextAsync(); var ex = await Assert.ThrowsAsync( @@ -177,7 +214,7 @@ public async Task connect_to_existing_with_start_from_set_to_valid_middle_positi var group = Fixture.GetGroupName(); var events = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, 10, userCredentials: TestCredentials.Root) + .ReadAllAsync(new ReadAllOptions { MaxCount = 10, UserCredentials = TestCredentials.Root }) .ToArrayAsync(); var expectedEvent = events[events.Length / 2]; //just a random event in the middle of the results @@ -188,7 +225,10 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var resolvedEvent = await subscription!.Messages.OfType() .Select(e => e.ResolvedEvent) @@ -205,10 +245,18 @@ await Fixture.Subscriptions.CreateToAllAsync( public async Task connect_with_retries() { // Arrange var group = Fixture.GetGroupName(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); // Act - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + var retryCount = await subscription.Messages.OfType() .SelectAwait( async e => { @@ -270,12 +318,15 @@ public async Task create_with_commit_position_equal_to_last_indexed_position() { var group = Fixture.GetGroupName(); // Act - var lastEvent = await Fixture.Streams.ReadAllAsync( - Direction.Backwards, - Position.End, - 1, - userCredentials: TestCredentials.Root - ).FirstAsync(); + var lastEvent = await Fixture.Streams + .ReadAllAsync( + new ReadAllOptions { + Direction = Direction.Backwards, + Position = Position.End, + MaxCount = 1, + UserCredentials = TestCredentials.Root + } + ).FirstAsync(); var lastCommitPosition = lastEvent.OriginalPosition?.CommitPosition ?? throw new(); await Fixture.Subscriptions.CreateToAllAsync( @@ -306,7 +357,11 @@ public async Task deleting_existing_with_subscriber() { await Fixture.Subscriptions.CreateToAllAsync(group, new(), userCredentials: TestCredentials.Root); await Fixture.Subscriptions.DeleteToAllAsync(group, userCredentials: TestCredentials.Root); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var subscription = + Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); Assert.True( await subscription.Messages.OfType().AnyAsync() @@ -348,7 +403,10 @@ await Fixture.Subscriptions.CreateToAllAsync( [RetryFact] public async Task deleting_nonexistent() { await Assert.ThrowsAsync( - () => Fixture.Subscriptions.DeleteToAllAsync(Guid.NewGuid().ToString(), userCredentials: TestCredentials.Root) + () => Fixture.Subscriptions.DeleteToAllAsync( + Guid.NewGuid().ToString(), + userCredentials: TestCredentials.Root + ) ); } @@ -360,10 +418,10 @@ public async Task happy_case_catching_up_to_link_to_events_manual_ack() { var events = Fixture.CreateTestEvents(eventWriteCount) .Select( - (e, i) => new EventData( - e.EventId, + (e, i) => new MessageData( SystemEventTypes.LinkTo, Encoding.UTF8.GetBytes($"{i}@test"), + messageId: e.MessageId, contentType: Constants.Metadata.ContentTypes.ApplicationOctetStream ) ) @@ -379,7 +437,12 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, bufferSize: bufferCount, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions + { BufferSize = bufferCount, UserCredentials = TestCredentials.Root } + ); + await subscription.Messages.OfType() .Take(events.Length) .ForEachAwaitAsync(e => subscription.Ack(e.ResolvedEvent)) @@ -404,7 +467,12 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, bufferSize: bufferCount, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions + { BufferSize = bufferCount, UserCredentials = TestCredentials.Root } + ); + await subscription!.Messages.OfType() .Take(events.Length) .ForEachAwaitAsync(e => subscription.Ack(e.ResolvedEvent)) @@ -425,7 +493,14 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, bufferSize: bufferCount, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } + ); + foreach (var e in events) { await Fixture.Streams.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.Any, new[] { e }); } @@ -495,8 +570,12 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); - var enumerator = subscription.Messages.GetAsyncEnumerator(); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + + var enumerator = subscription.Messages.GetAsyncEnumerator(); await enumerator.MoveNextAsync(); await Task.WhenAll(Subscribe().WithTimeout(), WaitForCheckpoint().WithTimeout()); @@ -512,7 +591,11 @@ await Fixture.Subscriptions.CreateToAllAsync( await Fixture.Streams.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.NoStream, new[] { e }); } - await using var sub = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var sub = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + var resumed = await sub.Messages.OfType() .Select(e => e.ResolvedEvent) .Take(1) @@ -541,8 +624,7 @@ async Task Subscribe() { async Task WaitForCheckpoint() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -574,8 +656,12 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); - var enumerator = subscription.Messages.GetAsyncEnumerator(); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + + var enumerator = subscription.Messages.GetAsyncEnumerator(); await enumerator.MoveNextAsync(); await Task.WhenAll(Subscribe().WithTimeout(), WaitForCheckpoint().WithTimeout()); @@ -591,7 +677,11 @@ await Fixture.Subscriptions.CreateToAllAsync( await Fixture.Streams.AppendToStreamAsync("test-" + Guid.NewGuid(), StreamState.NoStream, [e]); } - await using var sub = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + await using var sub = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + var resumed = await sub.Messages.OfType() .Select(e => e.ResolvedEvent) .Take(1) @@ -620,8 +710,7 @@ async Task Subscribe() { async Task WaitForCheckpoint() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -646,10 +735,12 @@ await Fixture.Subscriptions.CreateToAllAsync( ); var lastEvent = await Fixture.Streams.ReadAllAsync( - Direction.Backwards, - Position.End, - 1, - userCredentials: TestCredentials.Root + new ReadAllOptions { + Direction = Direction.Backwards, + Position = Position.End, + MaxCount = 1, + UserCredentials = TestCredentials.Root + } ).FirstAsync(); var lastCommitPosition = lastEvent.OriginalPosition?.CommitPosition ?? throw new(); @@ -664,9 +755,16 @@ await Fixture.Subscriptions.UpdateToAllAsync( public async Task update_existing_with_subscribers() { var group = Fixture.GetGroupName(); - await Fixture.Subscriptions.CreateToAllAsync(group, new(startFrom: Position.Start), userCredentials: TestCredentials.Root); + await Fixture.Subscriptions.CreateToAllAsync( + group, + new(startFrom: Position.Start), + userCredentials: TestCredentials.Root + ); - var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); + var subscription = Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var enumerator = subscription.Messages.GetAsyncEnumerator(); @@ -707,8 +805,13 @@ await Fixture.Subscriptions.CreateToAllAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToAll(group, userCredentials: TestCredentials.Root); - await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + await using var subscription = + Fixture.Subscriptions.SubscribeToAll( + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); await Task.WhenAll(Subscribe().WithTimeout(), WaitForCheckpoints().WithTimeout()); @@ -721,7 +824,7 @@ await Fixture.Streams.AppendToStreamAsync( } Assert.True(secondCheckPoint > firstCheckPoint); - Assert.Equal(events.Select(e => e.EventId), appearedEvents.Select(e => e.Event.EventId)); + Assert.Equal(events.Select(e => e.MessageId), appearedEvents.Select(e => e.Event.EventId)); return; @@ -743,8 +846,7 @@ async Task WaitForCheckpoints() { bool firstCheckpointSet = false; await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-$all::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -832,10 +934,12 @@ public async Task create_with_commit_position_larger_than_last_indexed_position( var group = Fixture.GetGroupName(); var lastEvent = await Fixture.Streams.ReadAllAsync( - Direction.Backwards, - Position.End, - 1, - userCredentials: TestCredentials.Root + new ReadAllOptions { + Direction = Direction.Backwards, + Position = Position.End, + MaxCount = 1, + UserCredentials = TestCredentials.Root + } ).FirstAsync(); var lastCommitPosition = lastEvent.OriginalPosition?.CommitPosition ?? throw new(); diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs index 17592f16c..4b5ee1e4a 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToAll/SubscribeToAllUpdateExistingWithCheckpointTest.cs @@ -1,6 +1,4 @@ -using KurrentDB.Client; using KurrentDB.Client.Tests.TestNode; -using KurrentDB.Client.Tests; namespace KurrentDB.Client.Tests.PersistentSubscriptions; @@ -25,7 +23,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); @@ -38,7 +40,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents(1)); - await using var sub = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var sub = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); var resolvedEvent = await sub.Messages .OfType() @@ -71,8 +77,7 @@ async Task Subscribe() { async Task WaitForCheckpoint() { await using var subscription = Fixture.Streams.SubscribeToStream( $"$persistentsubscription-{stream}::{group}-checkpoint", - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs index 01e2aa457..7fddd698a 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamGetInfoObsoleteTests.cs @@ -29,7 +29,11 @@ public static IEnumerable AllowedUsers() { [Theory] [MemberData(nameof(AllowedUsers))] public async Task returns_expected_result(UserCredentials credentials) { - var result = await fixture.Subscriptions.GetInfoToStreamAsync(fixture.Stream, fixture.Group, userCredentials: credentials); + var result = await fixture.Subscriptions.GetInfoToStreamAsync( + fixture.Stream, + fixture.Group, + userCredentials: credentials + ); Assert.Equal(fixture.Stream, result.EventSource); Assert.Equal(fixture.Group, result.GroupName); @@ -179,15 +183,15 @@ await Subscriptions.SubscribeToStreamAsync( return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); for (var i = 0; i < 15; i++) { await Streams.AppendToStreamAsync( Stream, StreamState.Any, - [new EventData(Uuid.NewUuid(), "test-event", ReadOnlyMemory.Empty)], - userCredentials: TestCredentials.Root + [new MessageData("test-event", ReadOnlyMemory.Empty)], + new AppendToStreamOptions { UserCredentials = TestCredentials.Root } ); } }; diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs index 3648697f8..79e60bc90 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/Obsolete/SubscribeToStreamObsoleteTests.cs @@ -22,7 +22,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( stream, group, delegate { return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ).WithTimeout(); var ex = await Assert.ThrowsAsync( @@ -31,7 +31,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( stream, group, delegate { return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); } ).WithTimeout(); @@ -56,9 +56,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - delegate { return Task.CompletedTask; }, - (s, reason, ex) => dropped.TrySetResult((reason, ex)), - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = delegate { return Task.CompletedTask; }, + SubscriptionDropped = (s, reason, ex) => dropped.TrySetResult((reason, ex)) + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ).WithTimeout(); Assert.NotNull(subscription); @@ -86,22 +88,24 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var firstEvent = firstEventSource.Task; var resolvedEvent = await firstEvent.WithTimeout(TimeSpan.FromSeconds(10)); Assert.Equal(StreamPosition.Start, resolvedEvent.Event.EventNumber); - Assert.Equal(events[0].EventId, resolvedEvent.Event.EventId); + Assert.Equal(events[0].MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -113,7 +117,7 @@ public async Task connect_to_existing_with_start_from_beginning_and_no_stream() var events = Fixture.CreateTestEvents().ToArray(); - var eventId = events.Single().EventId; + var eventId = events.Single().MessageId; await Fixture.Subscriptions.CreateToStreamAsync( stream, @@ -125,15 +129,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); @@ -162,15 +168,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var firstEvent = firstEventSource.Task; @@ -198,15 +206,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -215,7 +225,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(); Assert.Equal(new(10), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -238,15 +248,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); var firstEvent = firstEventSource.Task; @@ -273,15 +285,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -290,7 +304,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(); Assert.Equal(new(10), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -312,22 +326,24 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); var firstEvent = firstEventSource.Task; var resolvedEvent = await firstEvent.WithTimeout(TimeSpan.FromSeconds(10)); - var eventId = events.Last().EventId; + var eventId = events.Last().MessageId; Assert.Equal(new(2), resolvedEvent.Event.EventNumber); Assert.Equal(eventId, resolvedEvent.Event.EventId); @@ -353,15 +369,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -370,7 +388,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(); Assert.Equal(new(4), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Skip(4).First().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Skip(4).First().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -393,15 +411,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -410,7 +430,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(); Assert.Equal(new(10), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -433,15 +453,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(10), events.Skip(11)); @@ -450,7 +472,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(); Assert.Equal(new(11), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -464,7 +486,7 @@ public async Task connect_to_non_existing_with_permissions() { stream, group, delegate { return Task.CompletedTask; }, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); } ).WithTimeout(); @@ -493,23 +515,25 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, r, ct) => { - if (r > 4) { - retryCountSource.TrySetResult(r.Value); - await subscription.Ack(e.Event.EventId); - } else { - await subscription.Nack( - PersistentSubscriptionNakEventAction.Retry, - "Not yet tried enough times", - e - ); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + if (r > 4) { + retryCountSource.TrySetResult(r.Value); + await subscription.Ack(e.Event.EventId); + } else { + await subscription.Nack( + PersistentSubscriptionNakEventAction.Retry, + "Not yet tried enough times", + e + ); + } + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + retryCountSource.TrySetException(ex!); } }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - retryCountSource.TrySetException(ex!); - }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); @@ -537,15 +561,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, r, ct) => { - firstEventSource.TrySetResult(e); - await subscription.Ack(e); - }, - (subscription, reason, ex) => { - if (reason != SubscriptionDroppedReason.Disposed) - firstEventSource.TrySetException(ex!); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, r, ct) => { + firstEventSource.TrySetResult(e); + await subscription.Ack(e); + }, + SubscriptionDropped = (subscription, reason, ex) => { + if (reason != SubscriptionDroppedReason.Disposed) + firstEventSource.TrySetException(ex!); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(10), events.Skip(11)); @@ -554,7 +580,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( var resolvedEvent = await firstEvent.WithTimeout(); Assert.Equal(new(11), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -573,9 +599,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - (_, _, _, _) => Task.CompletedTask, - (_, r, e) => dropped.TrySetResult((r, e)), - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = (_, _, _, _) => Task.CompletedTask, + SubscriptionDropped = (_, r, e) => dropped.TrySetResult((r, e)) + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Subscriptions.DeleteToStreamAsync( @@ -608,9 +636,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - (_, _, _, _) => Task.CompletedTask, - (_, r, e) => _dropped.TrySetResult((r, e)), - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = (_, _, _, _) => Task.CompletedTask, + SubscriptionDropped = (_, r, e) => _dropped.TrySetResult((r, e)) + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Subscriptions.DeleteToStreamAsync( @@ -636,23 +666,23 @@ public async Task happy_case_catching_up_to_link_to_events_manual_ack() { const int bufferCount = 10; const int eventWriteCount = bufferCount * 2; - EventData[] events; + MessageData[] events; TaskCompletionSource eventsReceived = new(); int eventReceivedCount = 0; events = Fixture.CreateTestEvents(eventWriteCount) .Select( - (e, i) => new EventData( - e.EventId, + (e, i) => new MessageData( SystemEventTypes.LinkTo, Encoding.UTF8.GetBytes($"{i}@{stream}"), + messageId: e.MessageId, contentType: Constants.Metadata.ContentTypes.ApplicationOctetStream ) ) .ToArray(); foreach (var e in events) - await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, new[] { e }); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, [e]); await Fixture.Subscriptions.CreateToStreamAsync( stream, @@ -664,18 +694,22 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, retryCount, ct) => { - await subscription.Ack(e); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, retryCount, ct) => { + await subscription.Ack(e); - if (Interlocked.Increment(ref eventReceivedCount) == events.Length) - eventsReceived.TrySetResult(true); - }, - (s, r, e) => { - if (e != null) - eventsReceived.TrySetException(e); + if (Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + SubscriptionDropped = (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + } }, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); await eventsReceived.Task.WithTimeout(); @@ -707,18 +741,22 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, retryCount, ct) => { - await subscription.Ack(e); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, retryCount, ct) => { + await subscription.Ack(e); - if (Interlocked.Increment(ref eventReceivedCount) == events.Length) - eventsReceived.TrySetResult(true); - }, - (s, r, e) => { - if (e != null) - eventsReceived.TrySetException(e); + if (Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + SubscriptionDropped = (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + } }, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); await eventsReceived.Task.WithTimeout(); @@ -746,17 +784,21 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, retryCount, ct) => { - await subscription.Ack(e); - if (Interlocked.Increment(ref eventReceivedCount) == events.Length) - eventsReceived.TrySetResult(true); - }, - (s, r, e) => { - if (e != null) - eventsReceived.TrySetException(e); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, retryCount, ct) => { + await subscription.Ack(e); + if (Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + SubscriptionDropped = (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + } }, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); foreach (var e in events) @@ -772,11 +814,11 @@ public async Task update_existing_with_check_point() { StreamPosition checkPoint = default; - TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> droppedSource = new(); - TaskCompletionSource resumedSource = new(); - TaskCompletionSource appeared = new(); + TaskCompletionSource<(SubscriptionDroppedReason, Exception?)> droppedSource = new(); + TaskCompletionSource resumedSource = new(); + TaskCompletionSource appeared = new(); List appearedEvents = []; - EventData[] events = Fixture.CreateTestEvents(5).ToArray(); + MessageData[] events = Fixture.CreateTestEvents(5).ToArray(); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); await Fixture.Subscriptions.CreateToStreamAsync( @@ -795,15 +837,17 @@ await Fixture.Subscriptions.CreateToStreamAsync( await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (s, e, _, _) => { - appearedEvents.Add(e); - await s.Ack(e); + new PersistentSubscriptionListener { + EventAppeared = async (s, e, _, _) => { + appearedEvents.Add(e); + await s.Ack(e); - if (appearedEvents.Count == events.Length) - appeared.TrySetResult(true); + if (appearedEvents.Count == events.Length) + appeared.TrySetResult(true); + }, + SubscriptionDropped = (_, reason, ex) => droppedSource.TrySetResult((reason, ex)) }, - (_, reason, ex) => droppedSource.TrySetResult((reason, ex)), - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Task.WhenAll(appeared.Task.WithTimeout(), Checkpointed()); @@ -821,17 +865,19 @@ await Fixture.Subscriptions.UpdateToStreamAsync( await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (s, e, _, _) => { - resumedSource.TrySetResult(e); - await s.Ack(e); - }, - (_, reason, ex) => { - if (ex is not null) - resumedSource.TrySetException(ex); - else - resumedSource.TrySetResult(default); + new PersistentSubscriptionListener { + EventAppeared = async (s, e, _, _) => { + resumedSource.TrySetResult(e); + await s.Ack(e); + }, + SubscriptionDropped = (_, reason, ex) => { + if (ex is not null) + resumedSource.TrySetException(ex); + else + resumedSource.TrySetResult(default); + } }, - TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents(1)); @@ -844,8 +890,7 @@ await Fixture.Subscriptions.SubscribeToStreamAsync( async Task Checkpointed() { await using var subscription = Fixture.Streams.SubscribeToStream( checkPointStream, - FromStream.Start, - userCredentials: TestCredentials.Root + new SubscribeToStreamOptions { UserCredentials = TestCredentials.Root } ); await foreach (var message in subscription.Messages) { @@ -879,9 +924,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - delegate { return Task.CompletedTask; }, - (_, reason, ex) => droppedSource.TrySetResult((reason, ex)), - TestCredentials.Root + new PersistentSubscriptionListener { + EventAppeared = delegate { return Task.CompletedTask; }, + SubscriptionDropped = (_, reason, ex) => droppedSource.TrySetResult((reason, ex)) + }, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Subscriptions.UpdateToStreamAsync( @@ -911,7 +958,7 @@ public async Task when_writing_and_subscribing_to_normal_events_manual_nack() { int eventReceivedCount = 0; - EventData[] events = Fixture.CreateTestEvents(eventWriteCount) + MessageData[] events = Fixture.CreateTestEvents(eventWriteCount) .ToArray(); TaskCompletionSource eventsReceived = new(); @@ -926,18 +973,22 @@ await Fixture.Subscriptions.CreateToStreamAsync( using var subscription = await Fixture.Subscriptions.SubscribeToStreamAsync( stream, group, - async (subscription, e, retryCount, ct) => { - await subscription.Nack(PersistentSubscriptionNakEventAction.Park, "fail", e); + new PersistentSubscriptionListener { + EventAppeared = async (subscription, e, retryCount, ct) => { + await subscription.Nack(PersistentSubscriptionNakEventAction.Park, "fail", e); - if (Interlocked.Increment(ref eventReceivedCount) == events.Length) - eventsReceived.TrySetResult(true); - }, - (s, r, e) => { - if (e != null) - eventsReceived.TrySetException(e); + if (Interlocked.Increment(ref eventReceivedCount) == events.Length) + eventsReceived.TrySetResult(true); + }, + SubscriptionDropped = (s, r, e) => { + if (e != null) + eventsReceived.TrySetException(e); + } }, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + UserCredentials = TestCredentials.Root, + BufferSize = bufferCount + } ); foreach (var e in events) diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs index 9380a1cc2..ee99f9f4a 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs @@ -23,7 +23,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Assert.ThrowsAsync( diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs index 100ff2e97..3b3d08101 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamGetInfoTests.cs @@ -31,7 +31,11 @@ public static IEnumerable AllowedUsers() { [Theory] [MemberData(nameof(AllowedUsers))] public async Task returns_expected_result(UserCredentials credentials) { - var result = await fixture.Subscriptions.GetInfoToStreamAsync(fixture.Stream, fixture.Group, userCredentials: credentials); + var result = await fixture.Subscriptions.GetInfoToStreamAsync( + fixture.Stream, + fixture.Group, + userCredentials: credentials + ); Assert.Equal(fixture.Stream, result.EventSource); Assert.Equal(fixture.Group, result.GroupName); @@ -153,7 +157,7 @@ public class CustomFixture : KurrentDBTemporaryFixture { public string Stream { get; set; } KurrentDBPersistentSubscriptionsClient.PersistentSubscriptionResult? Subscription; - IAsyncEnumerator? Enumerator; + IAsyncEnumerator? Enumerator; public CustomFixture() : base(x => x.WithoutDefaultCredentials()) { Group = GetGroupName(); @@ -168,15 +172,20 @@ await Subscriptions.CreateToStreamAsync( ); var counter = 0; - Subscription = Subscriptions.SubscribeToStream(Stream, Group, userCredentials: TestCredentials.Root); - Enumerator = Subscription.Messages.GetAsyncEnumerator(); + Subscription = Subscriptions.SubscribeToStream( + Stream, + Group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + + Enumerator = Subscription.Messages.GetAsyncEnumerator(); for (var i = 0; i < 15; i++) { await Streams.AppendToStreamAsync( Stream, StreamState.Any, - [new EventData(Uuid.NewUuid(), "test-event", ReadOnlyMemory.Empty)], - userCredentials: TestCredentials.Root + [new MessageData("test-event", ReadOnlyMemory.Empty)], + new AppendToStreamOptions { UserCredentials = TestCredentials.Root } ); } diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamNoDefaultCredentialsTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamNoDefaultCredentialsTests.cs index 3893c5d56..39a42258c 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamNoDefaultCredentialsTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamNoDefaultCredentialsTests.cs @@ -57,7 +57,7 @@ await Fixture.Streams.AppendToStreamAsync( stream, StreamState.NoStream, Fixture.CreateTestEvents(), - userCredentials: TestCredentials.Root + new AppendToStreamOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Subscriptions.CreateToStreamAsync( diff --git a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs index f611d8e46..e560a66ed 100644 --- a/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs +++ b/test/KurrentDB.Client.Tests/PersistentSubscriptions/SubscribeToStream/SubscribeToStreamTests.cs @@ -39,14 +39,21 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - var ex = await Assert.ThrowsAsync(() => Task.WhenAll(Subscribe().WithTimeout(), Subscribe().WithTimeout())); + var ex = await Assert.ThrowsAsync( + () => Task.WhenAll(Subscribe().WithTimeout(), Subscribe().WithTimeout()) + ); Assert.Equal(stream, ex.StreamName); Assert.Equal(group, ex.GroupName); return; async Task Subscribe() { - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + await subscription.Messages.AnyAsync(); } } @@ -58,9 +65,16 @@ public async Task connect_to_existing_with_permissions() { await Fixture.Subscriptions.CreateToStreamAsync(stream, group, new(), userCredentials: TestCredentials.Root); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); - Assert.True(await subscription.Messages.FirstAsync().AsTask().WithTimeout() is PersistentSubscriptionMessage.SubscriptionConfirmation); + Assert.True( + await subscription.Messages.FirstAsync().AsTask().WithTimeout() is PersistentSubscriptionMessage + .SubscriptionConfirmation + ); } [RetryFact] @@ -77,21 +91,26 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + var resolvedEvent = await subscription.Messages.OfType() .Select(e => e.ResolvedEvent) .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(StreamPosition.Start, resolvedEvent.Event.EventNumber); - Assert.Equal(events[0].EventId, resolvedEvent.Event.EventId); + Assert.Equal(events[0].MessageId, resolvedEvent.Event.EventId); } [RetryFact] public async Task connect_to_existing_with_start_from_beginning_and_no_stream() { - var stream = Fixture.GetStreamName(); - var group = Fixture.GetGroupName(); - var events = Fixture.CreateTestEvents().ToArray(); - var eventId = events.Single().EventId; + var stream = Fixture.GetStreamName(); + var group = Fixture.GetGroupName(); + var events = Fixture.CreateTestEvents().ToArray(); + var messageId = events.Single().MessageId; await Fixture.Subscriptions.CreateToStreamAsync( stream, @@ -100,7 +119,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); @@ -109,7 +132,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(StreamPosition.Start, resolvedEvent.Event.EventNumber); - Assert.Equal(eventId, resolvedEvent.Event.EventId); + Assert.Equal(messageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -129,7 +152,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -139,7 +162,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(new(10), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -159,11 +182,12 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Assert.ThrowsAsync( - () => subscription.Messages.AnyAsync(message => message is PersistentSubscriptionMessage.Event).AsTask().WithTimeout(TimeSpan.FromMilliseconds(250)) + () => subscription.Messages.AnyAsync(message => message is PersistentSubscriptionMessage.Event).AsTask() + .WithTimeout(TimeSpan.FromMilliseconds(250)) ); } @@ -181,7 +205,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.TestUser1); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.TestUser1 } + ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); } @@ -192,7 +220,7 @@ public async Task connect_to_existing_with_start_from_two_and_no_stream() { var stream = Fixture.GetStreamName(); var group = Fixture.GetGroupName(); - var eventId = events.Last().EventId; + var messageId = events.Last().MessageId; await Fixture.Subscriptions.CreateToStreamAsync( stream, @@ -204,7 +232,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); @@ -214,7 +242,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(new(2), resolvedEvent.Event.EventNumber); - Assert.Equal(eventId, resolvedEvent.Event.EventId); + Assert.Equal(messageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -234,7 +262,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -245,7 +273,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(new(4), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Skip(4).First().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Skip(4).First().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -265,7 +293,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(9), events.Skip(10)); @@ -275,7 +303,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(new(10), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -295,7 +323,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(10), events.Skip(11)); @@ -305,7 +333,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(new(11), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -313,7 +341,12 @@ public async Task connect_to_non_existing_with_permissions() { var stream = Fixture.GetStreamName(); var group = Fixture.GetGroupName(); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + Assert.True( await subscription.Messages.OfType() .AnyAsync() @@ -336,7 +369,12 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); var retryCount = await subscription.Messages.OfType() .SelectAwait( @@ -376,7 +414,11 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(10), events.Skip(11)); @@ -385,7 +427,7 @@ await Fixture.Subscriptions.CreateToStreamAsync( .FirstOrDefaultAsync().AsTask().WithTimeout(); Assert.Equal(new(11), resolvedEvent.Event.EventNumber); - Assert.Equal(events.Last().EventId, resolvedEvent.Event.EventId); + Assert.Equal(events.Last().MessageId, resolvedEvent.Event.EventId); } [RetryFact] @@ -491,7 +533,11 @@ public async Task deleting_existing_with_subscriber() { await Fixture.Subscriptions.DeleteToStreamAsync(stream, group, userCredentials: TestCredentials.Root); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); Assert.True( await subscription.Messages.OfType().AnyAsync() @@ -520,10 +566,10 @@ public async Task happy_case_catching_up_to_link_to_events_manual_ack() { var events = Fixture.CreateTestEvents(eventWriteCount) .Select( - (e, i) => new EventData( - e.EventId, + (e, i) => new MessageData( SystemEventTypes.LinkTo, Encoding.UTF8.GetBytes($"{i}@{stream}"), + messageId: e.MessageId, contentType: Constants.Metadata.ContentTypes.ApplicationOctetStream ) ).ToArray(); @@ -541,8 +587,10 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); await subscription!.Messages.OfType() @@ -562,7 +610,7 @@ public async Task happy_case_catching_up_to_normal_events_manual_ack() { var events = Fixture.CreateTestEvents(eventWriteCount).ToArray(); foreach (var e in events) - await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, new[] { e }); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, [e]); await Fixture.Subscriptions.CreateToStreamAsync( stream, @@ -574,8 +622,10 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); await subscription!.Messages.OfType() @@ -604,8 +654,10 @@ await Fixture.Subscriptions.CreateToStreamAsync( await using var subscription = Fixture.Subscriptions.SubscribeToStream( stream, group, - bufferSize: bufferCount, - userCredentials: TestCredentials.Root + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } ); foreach (var e in events) @@ -651,8 +703,13 @@ public async Task update_existing_with_subscribers() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents()); await Fixture.Subscriptions.CreateToStreamAsync(stream, group, new(), userCredentials: TestCredentials.Root); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, userCredentials: TestCredentials.Root); - await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { UserCredentials = TestCredentials.Root } + ); + + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); await enumerator.MoveNextAsync(); @@ -700,7 +757,14 @@ await Fixture.Subscriptions.CreateToStreamAsync( userCredentials: TestCredentials.Root ); - await using var subscription = Fixture.Subscriptions.SubscribeToStream(stream, group, bufferSize: bufferCount, userCredentials: TestCredentials.Root); + await using var subscription = Fixture.Subscriptions.SubscribeToStream( + stream, + group, + new SubscribeToPersistentSubscriptionOptions { + BufferSize = bufferCount, + UserCredentials = TestCredentials.Root + } + ); foreach (var e in events) await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, [e]); diff --git a/test/KurrentDB.Client.Tests/Security/AllStreamWithNoAclSecurityTests.cs b/test/KurrentDB.Client.Tests/Security/AllStreamWithNoAclSecurityTests.cs index 2abc3645e..3d0d1c52a 100644 --- a/test/KurrentDB.Client.Tests/Security/AllStreamWithNoAclSecurityTests.cs +++ b/test/KurrentDB.Client.Tests/Security/AllStreamWithNoAclSecurityTests.cs @@ -1,24 +1,35 @@ -using KurrentDB.Client; using KurrentDB.Client.Tests.TestNode; -using KurrentDB.Client.Tests; namespace KurrentDB.Client.Tests; [Trait("Category", "Target:Security")] -public class AllStreamWithNoAclSecurityTests(ITestOutputHelper output, AllStreamWithNoAclSecurityTests.CustomFixture fixture) +public class AllStreamWithNoAclSecurityTests( + ITestOutputHelper output, + AllStreamWithNoAclSecurityTests.CustomFixture fixture +) : KurrentTemporaryTests(output, fixture) { [Fact] public async Task write_to_all_is_never_allowed() { await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.AllStream)); - await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => Fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestAdmin)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestUser1) + ); + + await Assert.ThrowsAsync( + () => Fixture.AppendStream(SecurityFixture.AllStream, TestCredentials.TestAdmin) + ); } [Fact] public async Task delete_of_all_is_never_allowed() { await Assert.ThrowsAsync(() => Fixture.DeleteStream(SecurityFixture.AllStream)); - await Assert.ThrowsAsync(() => Fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => Fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestAdmin)); + await Assert.ThrowsAsync( + () => Fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestUser1) + ); + + await Assert.ThrowsAsync( + () => Fixture.DeleteStream(SecurityFixture.AllStream, TestCredentials.TestAdmin) + ); } [Fact] @@ -33,7 +44,10 @@ public async Task reading_and_subscribing_is_not_allowed_when_no_credentials_are public async Task reading_and_subscribing_is_not_allowed_for_usual_user() { await Assert.ThrowsAsync(() => Fixture.ReadAllForward(TestCredentials.TestUser1)); await Assert.ThrowsAsync(() => Fixture.ReadAllBackward(TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => Fixture.ReadMeta(SecurityFixture.AllStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync( + () => Fixture.ReadMeta(SecurityFixture.AllStream, TestCredentials.TestUser1) + ); + await Assert.ThrowsAsync(() => Fixture.SubscribeToAll(TestCredentials.TestUser1)); } @@ -51,7 +65,9 @@ public async Task meta_write_is_not_allowed_when_no_credentials_are_passed() => [Fact] public async Task meta_write_is_not_allowed_for_usual_user() => - await Assert.ThrowsAsync(() => Fixture.WriteMeta(SecurityFixture.AllStream, TestCredentials.TestUser1)); + await Assert.ThrowsAsync( + () => Fixture.WriteMeta(SecurityFixture.AllStream, TestCredentials.TestUser1) + ); [Fact] public Task meta_write_is_allowed_for_admin_user() => @@ -61,7 +77,12 @@ public class CustomFixture : SecurityFixture { protected override async Task Given() { await base.Given(); - await Streams.SetStreamMetadataAsync(AllStream, StreamState.Any, new(), userCredentials: TestCredentials.Root); + await Streams.SetStreamMetadataAsync( + AllStream, + StreamState.Any, + new(), + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } + ); } } } diff --git a/test/KurrentDB.Client.Tests/Security/MultipleRoleSecurityTests.cs b/test/KurrentDB.Client.Tests/Security/MultipleRoleSecurityTests.cs index 8d30f2f8a..103e40dc9 100644 --- a/test/KurrentDB.Client.Tests/Security/MultipleRoleSecurityTests.cs +++ b/test/KurrentDB.Client.Tests/Security/MultipleRoleSecurityTests.cs @@ -10,13 +10,24 @@ public class MultipleRoleSecurityTests(ITestOutputHelper output, MultipleRoleSec [Fact] public async Task multiple_roles_are_handled_correctly() { await Assert.ThrowsAsync(() => Fixture.ReadEvent("usr-stream")); - await Assert.ThrowsAsync(() => Fixture.ReadEvent("usr-stream", TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => Fixture.ReadEvent("usr-stream", TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => Fixture.ReadEvent("usr-stream", TestCredentials.TestAdmin)); + await Assert.ThrowsAsync( + () => Fixture.ReadEvent("usr-stream", TestCredentials.TestUser1) + ); + + await Assert.ThrowsAsync( + () => Fixture.ReadEvent("usr-stream", TestCredentials.TestUser2) + ); + + await Assert.ThrowsAsync( + () => Fixture.ReadEvent("usr-stream", TestCredentials.TestAdmin) + ); await Assert.ThrowsAsync(() => Fixture.AppendStream("usr-stream")); await Fixture.AppendStream("usr-stream", TestCredentials.TestUser1); - await Assert.ThrowsAsync(() => Fixture.AppendStream("usr-stream", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("usr-stream", TestCredentials.TestUser2) + ); + await Fixture.AppendStream("usr-stream", TestCredentials.TestAdmin); await Fixture.DeleteStream("usr-stream2", TestCredentials.TestUser1); @@ -32,13 +43,16 @@ public class CustomFixture : SecurityFixture { protected override async Task When() { var settings = new SystemSettings( new( - new[] { "user1", "user2" }, - new[] { "$admins", "user1" }, - new[] { "user1", SystemRoles.All } + ["user1", "user2"], + ["$admins", "user1"], + ["user1", SystemRoles.All] ) ); - await Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + await Streams.SetSystemSettingsAsync( + settings, + new SetSystemSettingsOptions { UserCredentials = TestCredentials.TestAdmin } + ); } } } diff --git a/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityForAllTests.cs b/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityForAllTests.cs index a3d5f6900..8b11c9854 100644 --- a/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityForAllTests.cs +++ b/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityForAllTests.cs @@ -5,7 +5,10 @@ namespace KurrentDB.Client.Tests; [Trait("Category", "Target:Security")] -public class OverridenSystemStreamSecurityForAllTests(ITestOutputHelper output, OverridenSystemStreamSecurityForAllTests.CustomFixture fixture) +public class OverridenSystemStreamSecurityForAllTests( + ITestOutputHelper output, + OverridenSystemStreamSecurityForAllTests.CustomFixture fixture +) : KurrentTemporaryTests(output, fixture) { [Fact] public async Task operations_on_system_stream_succeeds_for_user() { @@ -68,7 +71,10 @@ protected override Task When() { ) ); - return Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + return Streams.SetSystemSettingsAsync( + settings, + new SetSystemSettingsOptions { UserCredentials = TestCredentials.TestAdmin } + ); } } } diff --git a/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityTests.cs b/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityTests.cs index f4875fccd..098db5cdf 100644 --- a/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityTests.cs +++ b/test/KurrentDB.Client.Tests/Security/OverridenSystemStreamSecurityTests.cs @@ -4,7 +4,10 @@ namespace KurrentDB.Client.Tests; [Trait("Category", "Target:Security")] -public class OverridenSystemStreamSecurityTests(ITestOutputHelper output, OverridenSystemStreamSecurityTests.CustomFixture fixture) +public class OverridenSystemStreamSecurityTests( + ITestOutputHelper output, + OverridenSystemStreamSecurityTests.CustomFixture fixture +) : KurrentTemporaryTests(output, fixture) { [Fact] public async Task operations_on_system_stream_succeed_for_authorized_user() { @@ -27,7 +30,10 @@ public async Task operations_on_system_stream_succeed_for_authorized_user() { public async Task operations_on_system_stream_fail_for_not_authorized_user() { var stream = $"${Fixture.GetStreamName()}"; await Assert.ThrowsAsync(() => Fixture.ReadEvent(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.ReadStreamForward(stream, TestCredentials.TestUser2) + ); + await Assert.ThrowsAsync( () => Fixture.ReadStreamBackward(stream, TestCredentials.TestUser2) @@ -38,7 +44,9 @@ await Assert.ThrowsAsync( await Assert.ThrowsAsync(() => Fixture.ReadMeta(stream, TestCredentials.TestUser2)); await Assert.ThrowsAsync(() => Fixture.WriteMeta(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.SubscribeToStream(stream, TestCredentials.TestUser2) + ); await Assert.ThrowsAsync(() => Fixture.DeleteStream(stream, TestCredentials.TestUser2)); } @@ -84,7 +92,10 @@ protected override Task When() { userStreamAcl: default ); - return Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + return Streams.SetSystemSettingsAsync( + settings, + new SetSystemSettingsOptions { UserCredentials = TestCredentials.TestAdmin } + ); } } } diff --git a/test/KurrentDB.Client.Tests/Security/OverridenUserStreamSecurityTests.cs b/test/KurrentDB.Client.Tests/Security/OverridenUserStreamSecurityTests.cs index c89cdad53..2ca80b559 100644 --- a/test/KurrentDB.Client.Tests/Security/OverridenUserStreamSecurityTests.cs +++ b/test/KurrentDB.Client.Tests/Security/OverridenUserStreamSecurityTests.cs @@ -4,7 +4,10 @@ namespace KurrentDB.Client.Tests; [Trait("Category", "Target:Security")] -public class OverridenUserStreamSecurityTests(ITestOutputHelper output, OverridenUserStreamSecurityTests.CustomFixture fixture) +public class OverridenUserStreamSecurityTests( + ITestOutputHelper output, + OverridenUserStreamSecurityTests.CustomFixture fixture +) : KurrentTemporaryTests(output, fixture) { [Fact] public async Task operations_on_user_stream_succeeds_for_authorized_user() { @@ -27,14 +30,21 @@ public async Task operations_on_user_stream_succeeds_for_authorized_user() { public async Task operations_on_user_stream_fail_for_not_authorized_user() { var stream = Fixture.GetStreamName(); await Assert.ThrowsAsync(() => Fixture.ReadEvent(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => Fixture.ReadStreamForward(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => Fixture.ReadStreamBackward(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.ReadStreamForward(stream, TestCredentials.TestUser2) + ); + + await Assert.ThrowsAsync( + () => Fixture.ReadStreamBackward(stream, TestCredentials.TestUser2) + ); await Assert.ThrowsAsync(() => Fixture.AppendStream(stream, TestCredentials.TestUser2)); await Assert.ThrowsAsync(() => Fixture.ReadMeta(stream, TestCredentials.TestUser2)); await Assert.ThrowsAsync(() => Fixture.WriteMeta(stream, TestCredentials.TestUser2)); - await Assert.ThrowsAsync(() => Fixture.SubscribeToStream(stream, TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.SubscribeToStream(stream, TestCredentials.TestUser2) + ); await Assert.ThrowsAsync(() => Fixture.DeleteStream(stream, TestCredentials.TestUser2)); } @@ -75,7 +85,10 @@ public async Task operations_on_user_stream_succeed_for_admin() { public class CustomFixture : SecurityFixture { protected override Task When() { var settings = new SystemSettings(new("user1", "user1", "user1", "user1", "user1")); - return Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + return Streams.SetSystemSettingsAsync( + settings, + new SetSystemSettingsOptions { UserCredentials = TestCredentials.TestAdmin } + ); } } } diff --git a/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs b/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs index 1d3982520..991b7be77 100644 --- a/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs +++ b/test/KurrentDB.Client.Tests/Security/SecurityFixture.cs @@ -52,42 +52,42 @@ await Streams.SetStreamMetadataAsync( NoAclStream, StreamState.NoStream, new(), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( ReadStream, StreamState.NoStream, new(acl: new(TestCredentials.TestUser1.Username)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( WriteStream, StreamState.NoStream, new(acl: new(writeRole: TestCredentials.TestUser1.Username)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( MetaReadStream, StreamState.NoStream, new(acl: new(metaReadRole: TestCredentials.TestUser1.Username)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( MetaWriteStream, StreamState.NoStream, new(acl: new(metaWriteRole: TestCredentials.TestUser1.Username)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( AllStream, StreamState.Any, new(acl: new(TestCredentials.TestUser1.Username)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( @@ -101,7 +101,7 @@ await Streams.SetStreamMetadataAsync( metaReadRole: TestCredentials.TestUser1.Username ) ), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( @@ -115,7 +115,7 @@ await Streams.SetStreamMetadataAsync( metaReadRole: SystemRoles.Admins ) ), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( @@ -129,7 +129,7 @@ await Streams.SetStreamMetadataAsync( metaReadRole: SystemRoles.All ) ), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); await Streams.SetStreamMetadataAsync( @@ -143,90 +143,73 @@ await Streams.SetStreamMetadataAsync( metaReadRole: SystemRoles.All ) ), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ).WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); } protected virtual Task When() => Task.CompletedTask; - public Task ReadEvent(string streamId, UserCredentials? userCredentials = default) => - Streams.ReadStreamAsync( - Direction.Forwards, - streamId, - StreamPosition.Start, - 1, - false, - userCredentials: userCredentials - ) + public Task ReadEvent(string streamId, UserCredentials? userCredentials = null) => + Streams.ReadStreamAsync(streamId, new ReadStreamOptions { MaxCount = 1, UserCredentials = userCredentials }) .ToArrayAsync() .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task ReadStreamForward(string streamId, UserCredentials? userCredentials = default) => - Streams.ReadStreamAsync( - Direction.Forwards, - streamId, - StreamPosition.Start, - 1, - false, - userCredentials: userCredentials - ) + public Task ReadStreamForward(string streamId, UserCredentials? userCredentials = null) => + Streams.ReadStreamAsync(streamId, new ReadStreamOptions { MaxCount = 1, UserCredentials = userCredentials }) .ToArrayAsync() .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task ReadStreamBackward(string streamId, UserCredentials? userCredentials = default) => + public Task ReadStreamBackward(string streamId, UserCredentials? userCredentials = null) => Streams.ReadStreamAsync( - Direction.Backwards, streamId, - StreamPosition.Start, - 1, - false, - userCredentials: userCredentials + new ReadStreamOptions { + Direction = Direction.Backwards, + StreamPosition = StreamPosition.End, + MaxCount = 1, + UserCredentials = userCredentials + } ) .ToArrayAsync() .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task AppendStream(string streamId, UserCredentials? userCredentials = default) => + public Task AppendStream(string streamId, UserCredentials? userCredentials = null) => Streams.AppendToStreamAsync( streamId, StreamState.Any, CreateTestEvents(3), - userCredentials: userCredentials + new AppendToStreamOptions { UserCredentials = userCredentials } ) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task ReadAllForward(UserCredentials? userCredentials = default) => - Streams.ReadAllAsync( - Direction.Forwards, - Position.Start, - 1, - false, - userCredentials: userCredentials - ) + public Task ReadAllForward(UserCredentials? userCredentials = null) => + Streams.ReadAllAsync(new ReadAllOptions { MaxCount = 1, UserCredentials = userCredentials }) .ToArrayAsync() .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task ReadAllBackward(UserCredentials? userCredentials = default) => - Streams - .ReadAllAsync( - Direction.Backwards, - Position.End, - 1, - false, - userCredentials: userCredentials + public Task ReadAllBackward(UserCredentials? userCredentials = null) => + Streams.ReadAllAsync( + new ReadAllOptions { + Direction = Direction.Backwards, + Position = Position.End, + MaxCount = 1, + UserCredentials = userCredentials + } ) .ToArrayAsync() .AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task ReadMeta(string streamId, UserCredentials? userCredentials = default) => - Streams.GetStreamMetadataAsync(streamId, userCredentials: userCredentials) + public Task ReadMeta(string streamId, UserCredentials? userCredentials = null) => + Streams.GetStreamMetadataAsync(streamId, new OperationOptions { UserCredentials = userCredentials }) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public Task WriteMeta(string streamId, UserCredentials? userCredentials = default, string? role = default) => + public Task WriteMeta( + string streamId, UserCredentials? userCredentials = null, string? role = null + ) => Streams.SetStreamMetadataAsync( streamId, StreamState.Any, @@ -238,41 +221,43 @@ public Task WriteMeta(string streamId, UserCredentials? userCreden metaReadRole: role ) ), - userCredentials: userCredentials + new SetStreamMetadataOptions { UserCredentials = userCredentials } ) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); - public async Task SubscribeToStream(string streamId, UserCredentials? userCredentials = default) { + public async Task SubscribeToStream(string streamId, UserCredentials? userCredentials = null) { await using var subscription = - Streams.SubscribeToStream(streamId, FromStream.Start, userCredentials: userCredentials); + Streams.SubscribeToStream(streamId, new SubscribeToStreamOptions { UserCredentials = userCredentials }); await subscription .Messages.OfType().AnyAsync().AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); } - public async Task SubscribeToAll(UserCredentials? userCredentials = default) { + public async Task SubscribeToAll(UserCredentials? userCredentials = null) { await using var subscription = - Streams.SubscribeToAll(FromAll.Start, userCredentials: userCredentials); + Streams.SubscribeToAll(new SubscribeToAllOptions { UserCredentials = userCredentials }); await subscription .Messages.OfType().AnyAsync().AsTask() .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); } - public async Task CreateStreamWithMeta(StreamMetadata metadataPermanent, [CallerMemberName] string streamId = "") { + public async Task CreateStreamWithMeta( + StreamMetadata metadataPermanent, [CallerMemberName] string streamId = "" + ) { await Streams.SetStreamMetadataAsync( streamId, StreamState.NoStream, metadataPermanent, - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.TestAdmin } ) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); return streamId; } - public Task DeleteStream(string streamId, UserCredentials? userCredentials = default) => - Streams.TombstoneAsync(streamId, StreamState.Any, userCredentials: userCredentials) + public Task DeleteStream(string streamId, UserCredentials? userCredentials = null) => + Streams.TombstoneAsync(streamId, StreamState.Any, new TombstoneOptions { UserCredentials = userCredentials }) .WithTimeout(TimeSpan.FromMilliseconds(TimeoutMs)); } diff --git a/test/KurrentDB.Client.Tests/Security/StreamSecurityInheritanceTests.cs b/test/KurrentDB.Client.Tests/Security/StreamSecurityInheritanceTests.cs index c15798794..460586c31 100644 --- a/test/KurrentDB.Client.Tests/Security/StreamSecurityInheritanceTests.cs +++ b/test/KurrentDB.Client.Tests/Security/StreamSecurityInheritanceTests.cs @@ -5,18 +5,25 @@ namespace KurrentDB.Client.Tests; [Trait("Category", "Target:Security")] -public class StreamSecurityInheritanceTests(ITestOutputHelper output, StreamSecurityInheritanceTests.CustomFixture fixture) +public class StreamSecurityInheritanceTests( + ITestOutputHelper output, + StreamSecurityInheritanceTests.CustomFixture fixture +) : KurrentTemporaryTests(output, fixture) { [RetryFact] public async Task acl_inheritance_is_working_properly_on_user_streams() { await Assert.ThrowsAsync(() => Fixture.AppendStream("user-no-acl")); await Fixture.AppendStream("user-no-acl", TestCredentials.TestUser1); - await Assert.ThrowsAsync(() => Fixture.AppendStream("user-no-acl", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("user-no-acl", TestCredentials.TestUser2) + ); await Fixture.AppendStream("user-no-acl", TestCredentials.TestAdmin); await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-diff")); - await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-diff", TestCredentials.TestUser1)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("user-w-diff", TestCredentials.TestUser1) + ); await Fixture.AppendStream("user-w-diff", TestCredentials.TestUser2); await Fixture.AppendStream("user-w-diff", TestCredentials.TestAdmin); @@ -28,9 +35,13 @@ public async Task acl_inheritance_is_working_properly_on_user_streams() { await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-restricted")); - await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-restricted", TestCredentials.TestUser1)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("user-w-restricted", TestCredentials.TestUser1) + ); - await Assert.ThrowsAsync(() => Fixture.AppendStream("user-w-restricted", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("user-w-restricted", TestCredentials.TestUser2) + ); await Fixture.AppendStream("user-w-restricted", TestCredentials.TestAdmin); @@ -45,7 +56,9 @@ public async Task acl_inheritance_is_working_properly_on_user_streams() { await Assert.ThrowsAsync(() => Fixture.ReadEvent("user-r-restricted")); await Fixture.AppendStream("user-r-restricted", TestCredentials.TestUser1); await Fixture.ReadEvent("user-r-restricted", TestCredentials.TestUser1); - await Assert.ThrowsAsync(() => Fixture.ReadEvent("user-r-restricted", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.ReadEvent("user-r-restricted", TestCredentials.TestUser2) + ); await Fixture.ReadEvent("user-r-restricted", TestCredentials.TestAdmin); } @@ -63,12 +76,16 @@ public async Task acl_inheritance_is_working_properly_on_user_streams_when_not_a public async Task acl_inheritance_is_working_properly_on_system_streams() { await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-no-acl")); await Fixture.AppendStream("$sys-no-acl", TestCredentials.TestUser1); - await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-no-acl", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("$sys-no-acl", TestCredentials.TestUser2) + ); await Fixture.AppendStream("$sys-no-acl", TestCredentials.TestAdmin); await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-diff")); - await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-diff", TestCredentials.TestUser1)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("$sys-w-diff", TestCredentials.TestUser1) + ); await Fixture.AppendStream("$sys-w-diff", TestCredentials.TestUser2); await Fixture.AppendStream("$sys-w-diff", TestCredentials.TestAdmin); @@ -79,8 +96,13 @@ public async Task acl_inheritance_is_working_properly_on_system_streams() { await Fixture.AppendStream("$sys-w-multiple", TestCredentials.TestAdmin); await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-restricted")); - await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestUser1)); - await Assert.ThrowsAsync(() => Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestUser1) + ); + + await Assert.ThrowsAsync( + () => Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestUser2) + ); await Fixture.AppendStream("$sys-w-restricted", TestCredentials.TestAdmin); @@ -89,15 +111,20 @@ public async Task acl_inheritance_is_working_properly_on_system_streams() { await Fixture.AppendStream("$sys-w-all", TestCredentials.TestAdmin); await Assert.ThrowsAsync(() => Fixture.ReadEvent("$sys-no-acl")); - await Assert.ThrowsAsync(() => Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestUser1)); + await Assert.ThrowsAsync( + () => Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestUser1) + ); - await Assert.ThrowsAsync(() => Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestUser2)); + await Assert.ThrowsAsync( + () => Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestUser2) + ); await Fixture.ReadEvent("$sys-no-acl", TestCredentials.TestAdmin); } [AnonymousAccess.Fact] - public async Task acl_inheritance_is_working_properly_on_system_streams_when_not_authenticated() => await Fixture.AppendStream("$sys-w-all"); + public async Task acl_inheritance_is_working_properly_on_system_streams_when_not_authenticated() => + await Fixture.AppendStream("$sys-w-all"); public class CustomFixture : SecurityFixture { protected override async Task When() { @@ -106,83 +133,86 @@ protected override async Task When() { new(writeRole: "user1") ); - await Streams.SetSystemSettingsAsync(settings, userCredentials: TestCredentials.TestAdmin); + await Streams.SetSystemSettingsAsync( + settings, + new SetSystemSettingsOptions { UserCredentials = TestCredentials.TestAdmin } + ); await Streams.SetStreamMetadataAsync( "user-no-acl", StreamState.NoStream, new(), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "user-w-diff", StreamState.NoStream, new(acl: new(writeRole: "user2")), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "user-w-multiple", StreamState.NoStream, - new(acl: new(writeRoles: new[] { "user1", "user2" })), - userCredentials: TestCredentials.TestAdmin + new(acl: new(writeRoles: ["user1", "user2"])), + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "user-w-restricted", StreamState.NoStream, - new(acl: new(writeRoles: Array.Empty())), - userCredentials: TestCredentials.TestAdmin + new(acl: new(writeRoles: [])), + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "user-w-all", StreamState.NoStream, new(acl: new(writeRole: SystemRoles.All)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "user-r-restricted", StreamState.NoStream, new(acl: new("user1")), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "$sys-no-acl", StreamState.NoStream, new(), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "$sys-w-diff", StreamState.NoStream, new(acl: new(writeRole: "user2")), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "$sys-w-multiple", StreamState.NoStream, - new(acl: new(writeRoles: new[] { "user1", "user2" })), - userCredentials: TestCredentials.TestAdmin + new(acl: new(writeRoles: ["user1", "user2"])), + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "$sys-w-restricted", StreamState.NoStream, - new(acl: new(writeRoles: Array.Empty())), - userCredentials: TestCredentials.TestAdmin + new(acl: new(writeRoles: [])), + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); await Streams.SetStreamMetadataAsync( "$sys-w-all", StreamState.NoStream, new(acl: new(writeRole: SystemRoles.All)), - userCredentials: TestCredentials.TestAdmin + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); } } diff --git a/test/KurrentDB.Client.Tests/Streams/AppendTests.cs b/test/KurrentDB.Client.Tests/Streams/AppendTests.cs index 3dc716e18..dbfef0046 100644 --- a/test/KurrentDB.Client.Tests/Streams/AppendTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/AppendTests.cs @@ -1,4 +1,3 @@ -using KurrentDB.Client; using Grpc.Core; using Humanizer; @@ -6,7 +5,8 @@ namespace KurrentDB.Client.Tests.Streams; [Trait("Category", "Target:Streams")] [Trait("Category", "Operation:Append")] -public class AppendTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) : KurrentPermanentTests(output, fixture) { +public class AppendTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) + : KurrentPermanentTests(output, fixture) { [Theory, ExpectedVersionCreateStreamTestCases] public async Task appending_zero_events(StreamState expectedStreamState) { var stream = $"{Fixture.GetStreamName()}_{expectedStreamState}"; @@ -16,14 +16,14 @@ public async Task appending_zero_events(StreamState expectedStreamState) { var writeResult = await Fixture.Streams.AppendToStreamAsync( stream, expectedStreamState, - Enumerable.Empty() + Enumerable.Empty() ); writeResult.NextExpectedStreamState.ShouldBe(StreamState.NoStream); } await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, iterations) + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = iterations }) .ShouldThrowAsync(ex => ex.Stream.ShouldBe(stream)); } @@ -36,14 +36,14 @@ public async Task appending_zero_events_again(StreamState expectedStreamState) { var writeResult = await Fixture.Streams.AppendToStreamAsync( stream, expectedStreamState, - Enumerable.Empty() + Enumerable.Empty() ); Assert.Equal(StreamState.NoStream, writeResult.NextExpectedStreamState); } await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, iterations) + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = iterations }) .ShouldThrowAsync(ex => ex.Stream.ShouldBe(stream)); } @@ -59,7 +59,7 @@ public async Task create_stream_expected_version_on_first_write_if_does_not_exis Assert.Equal(new(0), writeResult.NextExpectedStreamState); - var count = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 2) + var count = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = 2 }) .CountAsync(); Assert.Equal(1, count); @@ -232,7 +232,7 @@ public async Task appending_with_wrong_expected_version_to_existing_stream_retur stream, StreamState.StreamRevision(1), Fixture.CreateTestEvents(), - options => { options.ThrowOnAppendFailure = false; } + new AppendToStreamOptions { ThrowOnAppendFailure = false } ); var wrongExpectedVersionResult = (WrongExpectedVersionResult)writeResult; @@ -305,7 +305,7 @@ public async Task stream, StreamState.StreamExists, Fixture.CreateTestEvents(), - options => { options.ThrowOnAppendFailure = false; } + new AppendToStreamOptions { ThrowOnAppendFailure = false } ); var wrongExpectedVersionResult = Assert.IsType(writeResult); @@ -361,7 +361,9 @@ public async Task returns_failure_status_when_conditionally_appending_with_versi ); Assert.Equal( - ConditionalWriteResult.FromWrongExpectedVersion(new(stream, StreamState.StreamRevision(7), StreamState.NoStream)), + ConditionalWriteResult.FromWrongExpectedVersion( + new(stream, StreamState.StreamRevision(7), StreamState.NoStream) + ), result ); } @@ -429,7 +431,7 @@ public async Task with_timeout_any_stream_revision_fails_when_operation_expired( stream, StreamState.Any, Fixture.CreateTestEvents(100), - deadline: TimeSpan.FromTicks(1) + new AppendToStreamOptions { Deadline = TimeSpan.FromTicks(1) } ).ShouldThrowAsync(); ex.StatusCode.ShouldBe(StatusCode.DeadlineExceeded); @@ -445,7 +447,7 @@ public async Task with_timeout_stream_revision_fails_when_operation_expired() { stream, StreamState.StreamRevision(0), Fixture.CreateTestEvents(10), - deadline: TimeSpan.Zero + new AppendToStreamOptions { Deadline = TimeSpan.Zero } ).ShouldThrowAsync(); ex.StatusCode.ShouldBe(StatusCode.DeadlineExceeded); @@ -460,11 +462,16 @@ await Fixture.Streams streamName, StreamState.Any, Fixture.CreateTestEventsThatThrowsException(), - userCredentials: new UserCredentials(TestCredentials.Root.Username!, TestCredentials.Root.Password!) + new AppendToStreamOptions { + UserCredentials = new UserCredentials( + TestCredentials.Root.Username!, + TestCredentials.Root.Password! + ) + } ) .ShouldThrowAsync(); - var state = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start) + var state = await Fixture.Streams.ReadStreamAsync(streamName) .ReadState; state.ShouldBe(ReadState.StreamNotFound); @@ -513,7 +520,8 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0em1_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); Assert.Equal(events.Length, count); } @@ -528,8 +536,8 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_4e4_0any_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); Assert.Equal(events.Length, count); } @@ -543,8 +551,9 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e5_non_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(5), events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 2).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 2 }) + .CountAsync(); + Assert.Equal(events.Length + 1, count); } @@ -556,7 +565,9 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e6_throws_wev() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - await Assert.ThrowsAsync(() => Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(6), events.Take(1))); + await Assert.ThrowsAsync( + () => Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(6), events.Take(1)) + ); } [RetryFact] @@ -571,7 +582,7 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e6_returns_wev() { stream, StreamState.StreamRevision(6), events.Take(1), - options => options.ThrowOnAppendFailure = false + new AppendToStreamOptions { ThrowOnAppendFailure = false } ); Assert.IsType(writeResult); @@ -585,7 +596,9 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e4_throws_wev() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - await Assert.ThrowsAsync(() => Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(4), events.Take(1))); + await Assert.ThrowsAsync( + () => Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(4), events.Take(1)) + ); } [RetryFact] @@ -600,7 +613,7 @@ public async Task sequence_0em1_1e0_2e1_3e2_4e3_5e4_0e4_returns_wev() { stream, StreamState.StreamRevision(4), events.Take(1), - options => options.ThrowOnAppendFailure = false + new AppendToStreamOptions { ThrowOnAppendFailure = false } ); Assert.IsType(writeResult); @@ -616,8 +629,9 @@ public async Task sequence_0em1_0e0_non_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(0), events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 2).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 2 }) + .CountAsync(); + Assert.Equal(events.Length + 1, count); } @@ -632,8 +646,8 @@ public async Task sequence_0em1_0any_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); Assert.Equal(events.Length, count); } @@ -647,8 +661,8 @@ public async Task sequence_0em1_0em1_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); Assert.Equal(events.Length, count); } @@ -663,8 +677,9 @@ public async Task sequence_0em1_1e0_2e1_1any_1any_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Skip(1).Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); + Assert.Equal(events.Length, count); } @@ -678,8 +693,9 @@ public async Task sequence_S_0em1_1em1_E_S_0em1_E_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); + Assert.Equal(events.Length, count); } @@ -693,8 +709,9 @@ public async Task sequence_S_0em1_1em1_E_S_0any_E_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); + Assert.Equal(events.Length, count); } @@ -709,8 +726,9 @@ public async Task sequence_S_0em1_1em1_E_S_1e0_E_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.StreamRevision(0), events.Skip(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); + Assert.Equal(events.Length, count); } @@ -724,8 +742,9 @@ public async Task sequence_S_0em1_1em1_E_S_1any_E_idempotent() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, events.Skip(1).Take(1)); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, events.Length + 1).CountAsync(); - + .ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = events.Length + 1 }) + .CountAsync(); + Assert.Equal(events.Length, count); } @@ -758,7 +777,7 @@ public async Task sequence_S_0em1_1em1_E_S_0em1_1em1_2em1_E_idempotancy_fail_ret stream, StreamState.NoStream, events, - options => options.ThrowOnAppendFailure = false + new AppendToStreamOptions { ThrowOnAppendFailure = false } ); Assert.IsType(writeResult); diff --git a/test/KurrentDB.Client.Tests/Streams/DeleteTests.cs b/test/KurrentDB.Client.Tests/Streams/DeleteTests.cs index 83903311f..b9f454692 100644 --- a/test/KurrentDB.Client.Tests/Streams/DeleteTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/DeleteTests.cs @@ -5,9 +5,12 @@ namespace KurrentDB.Client.Tests.Streams; [Trait("Category", "Target:Streams")] [Trait("Category", "Operation:Delete")] -public class DeleteTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) : KurrentPermanentTests(output, fixture) { +public class DeleteTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) + : KurrentPermanentTests(output, fixture) { [Theory, ExpectedStreamStateCases] - public async Task hard_deleting_a_stream_that_does_not_exist_with_expected_version_does_not_throw(StreamState expectedVersion, string name) { + public async Task hard_deleting_a_stream_that_does_not_exist_with_expected_version_does_not_throw( + StreamState expectedVersion, string name + ) { var stream = $"{Fixture.GetStreamName()}_{name}"; await Fixture.Streams.TombstoneAsync(stream, expectedVersion); @@ -26,14 +29,18 @@ public async Task soft_deleting_a_stream_that_exists() { public async Task hard_deleting_a_stream_that_does_not_exist_with_wrong_expected_version_throws() { var stream = Fixture.GetStreamName(); - await Assert.ThrowsAsync(() => Fixture.Streams.TombstoneAsync(stream, StreamState.StreamRevision(0))); + await Assert.ThrowsAsync( + () => Fixture.Streams.TombstoneAsync(stream, StreamState.StreamRevision(0)) + ); } [Fact] public async Task soft_deleting_a_stream_that_does_not_exist_with_wrong_expected_version_throws() { var stream = Fixture.GetStreamName(); - await Assert.ThrowsAsync(() => Fixture.Streams.DeleteAsync(stream, StreamState.StreamRevision(0))); + await Assert.ThrowsAsync( + () => Fixture.Streams.DeleteAsync(stream, StreamState.StreamRevision(0)) + ); } [Fact] @@ -72,13 +79,17 @@ public async Task hard_deleting_a_deleted_stream_should_throw() { await Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream); - await Assert.ThrowsAsync(() => Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream)); + await Assert.ThrowsAsync( + () => Fixture.Streams.TombstoneAsync(stream, StreamState.NoStream) + ); } [Fact] public async Task with_timeout_any_stream_revision_delete_fails_when_operation_expired() { - var stream = Fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync(() => Fixture.Streams.DeleteAsync(stream, StreamState.Any, TimeSpan.Zero)); + var stream = Fixture.GetStreamName(); + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.DeleteAsync(stream, StreamState.Any, new DeleteOptions { Deadline = TimeSpan.Zero }) + ); Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); } @@ -87,15 +98,27 @@ public async Task with_timeout_any_stream_revision_delete_fails_when_operation_e public async Task with_timeout_stream_revision_delete_fails_when_operation_expired() { var stream = Fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync(() => Fixture.Streams.DeleteAsync(stream, StreamState.StreamRevision(0), TimeSpan.Zero)); + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.DeleteAsync( + stream, + StreamState.StreamRevision(0), + new DeleteOptions { Deadline = TimeSpan.Zero } + ) + ); Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); } [Fact] public async Task with_timeout_any_stream_revision_tombstoning_fails_when_operation_expired() { - var stream = Fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync(() => Fixture.Streams.TombstoneAsync(stream, StreamState.Any, TimeSpan.Zero)); + var stream = Fixture.GetStreamName(); + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.TombstoneAsync( + stream, + StreamState.Any, + new TombstoneOptions { Deadline = TimeSpan.Zero } + ) + ); Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); } @@ -104,7 +127,13 @@ public async Task with_timeout_any_stream_revision_tombstoning_fails_when_operat public async Task with_timeout_stream_revision_tombstoning_fails_when_operation_expired() { var stream = Fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync(() => Fixture.Streams.TombstoneAsync(stream, StreamState.StreamRevision(0), TimeSpan.Zero)); + var rpcException = await Assert.ThrowsAsync( + () => Fixture.Streams.TombstoneAsync( + stream, + StreamState.StreamRevision(0), + new TombstoneOptions { Deadline = TimeSpan.Zero } + ) + ); Assert.Equal(StatusCode.DeadlineExceeded, rpcException.StatusCode); } diff --git a/test/KurrentDB.Client.Tests/Streams/Read/EventBinaryData.cs b/test/KurrentDB.Client.Tests/Streams/Read/EventBinaryData.cs deleted file mode 100644 index 15edd42b8..000000000 --- a/test/KurrentDB.Client.Tests/Streams/Read/EventBinaryData.cs +++ /dev/null @@ -1,35 +0,0 @@ -using KurrentDB.Client; - -namespace KurrentDB.Client.Tests; - -public readonly record struct EventBinaryData(Uuid Id, byte[] Data, byte[] Metadata) { - public bool Equals(EventBinaryData other) => - Id.Equals(other.Id) - && Data.SequenceEqual(other.Data) - && Metadata.SequenceEqual(other.Metadata); - - public override int GetHashCode() => System.HashCode.Combine(Id, Data, Metadata); -} - -public static class EventBinaryDataConverters { - public static EventBinaryData ToBinaryData(this EventData source) => - new(source.EventId, source.Data.ToArray(), source.Metadata.ToArray()); - - public static EventBinaryData ToBinaryData(this EventRecord source) => - new(source.EventId, source.Data.ToArray(), source.Metadata.ToArray()); - - public static EventBinaryData ToBinaryData(this ResolvedEvent source) => - source.Event.ToBinaryData(); - - public static EventBinaryData[] ToBinaryData(this IEnumerable source) => - source.Select(x => x.ToBinaryData()).ToArray(); - - public static EventBinaryData[] ToBinaryData(this IEnumerable source) => - source.Select(x => x.ToBinaryData()).ToArray(); - - public static EventBinaryData[] ToBinaryData(this IEnumerable source) => - source.Select(x => x.ToBinaryData()).ToArray(); - - public static ValueTask ToBinaryData(this IAsyncEnumerable source) => - source.DefaultIfEmpty().Select(x => x.ToBinaryData()).ToArrayAsync(); -} diff --git a/test/KurrentDB.Client.Tests/Streams/Read/MessageBinaryData.cs b/test/KurrentDB.Client.Tests/Streams/Read/MessageBinaryData.cs new file mode 100644 index 000000000..918c19818 --- /dev/null +++ b/test/KurrentDB.Client.Tests/Streams/Read/MessageBinaryData.cs @@ -0,0 +1,33 @@ +namespace KurrentDB.Client.Tests; + +public readonly record struct MessageBinaryData(Uuid Id, byte[] Data, byte[] Metadata) { + public bool Equals(MessageBinaryData other) => + Id.Equals(other.Id) + && Data.SequenceEqual(other.Data) + && Metadata.SequenceEqual(other.Metadata); + + public override int GetHashCode() => System.HashCode.Combine(Id, Data, Metadata); +} + +public static class EventBinaryDataConverters { + public static MessageBinaryData ToBinaryData(this MessageData source) => + new(source.MessageId, source.Data.ToArray(), source.Metadata.ToArray()); + + public static MessageBinaryData ToBinaryData(this EventRecord source) => + new(source.EventId, source.Data.ToArray(), source.Metadata.ToArray()); + + public static MessageBinaryData ToBinaryData(this ResolvedEvent source) => + source.Event.ToBinaryData(); + + public static MessageBinaryData[] ToBinaryData(this IEnumerable source) => + source.Select(x => x.ToBinaryData()).ToArray(); + + public static MessageBinaryData[] ToBinaryData(this IEnumerable source) => + source.Select(x => x.ToBinaryData()).ToArray(); + + public static MessageBinaryData[] ToBinaryData(this IEnumerable source) => + source.Select(x => x.ToBinaryData()).ToArray(); + + public static ValueTask ToBinaryData(this IAsyncEnumerable source) => + source.DefaultIfEmpty().Select(x => x.ToBinaryData()).ToArrayAsync(); +} diff --git a/test/KurrentDB.Client.Tests/Streams/Read/EventDataComparer.cs b/test/KurrentDB.Client.Tests/Streams/Read/MessageDataComparer.cs similarity index 65% rename from test/KurrentDB.Client.Tests/Streams/Read/EventDataComparer.cs rename to test/KurrentDB.Client.Tests/Streams/Read/MessageDataComparer.cs index fc36d3609..d7f48e991 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/EventDataComparer.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/MessageDataComparer.cs @@ -2,9 +2,9 @@ namespace KurrentDB.Client.Tests; -static class EventDataComparer { - public static bool Equal(EventData expected, EventRecord actual) { - if (expected.EventId != actual.EventId) +static class MessageDataComparer { + public static bool Equal(MessageData expected, EventRecord actual) { + if (expected.MessageId != actual.EventId) return false; if (expected.Type != actual.EventType) @@ -14,7 +14,7 @@ public static bool Equal(EventData expected, EventRecord actual) { && expected.Metadata.ToArray().SequenceEqual(actual.Metadata.ToArray()); } - public static bool Equal(EventData[] expected, EventRecord[] actual) { + public static bool Equal(MessageData[] expected, EventRecord[] actual) { if (expected.Length != actual.Length) return false; diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsBackwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsBackwardTests.cs index c27957e85..0c686ed8e 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsBackwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsBackwardTests.cs @@ -13,15 +13,21 @@ public class ReadAllEventsBackwardTests(ITestOutputHelper output, ReadAllEventsF : KurrentTemporaryTests(output, fixture) { [Fact] public async Task return_empty_if_reading_from_start() { - var result = await Fixture.Streams.ReadAllAsync(Direction.Backwards, Position.Start, 1).CountAsync(); + var result = await Fixture.Streams.ReadAllAsync( + new ReadAllOptions { Direction = Direction.Backwards, Position = Position.Start, MaxCount = 1 } + ).CountAsync(); + result.ShouldBe(0); } [Fact] public async Task return_partial_slice_if_not_enough_events() { - var count = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start).CountAsync(); + var count = await Fixture.Streams.ReadAllAsync().CountAsync(); var sliceSize = count * 2; - var result = await Fixture.Streams.ReadAllAsync(Direction.Backwards, Position.End, sliceSize).ToArrayAsync(); + var result = await Fixture.Streams.ReadAllAsync( + new ReadAllOptions { Direction = Direction.Backwards, Position = Position.End, MaxCount = sliceSize } + ).ToArrayAsync(); + result.Length.ShouldBeLessThan(sliceSize); } @@ -29,7 +35,7 @@ public async Task return_partial_slice_if_not_enough_events() { public async Task return_events_in_reversed_order_compared_to_written() { // TODO SS: this test must be fixed to deterministically read the expected events regardless of how many already exist. reading all for now... var result = await Fixture.Streams - .ReadAllAsync(Direction.Backwards, Position.End) + .ReadAllAsync(new ReadAllOptions().FromEnd()) .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) .ToBinaryData(); @@ -39,7 +45,7 @@ public async Task return_events_in_reversed_order_compared_to_written() { [Fact] public async Task return_single_event() { var result = await Fixture.Streams - .ReadAllAsync(Direction.Backwards, Position.End, 1) + .ReadAllAsync(new ReadAllOptions().Last()) .ToArrayAsync(); result.ShouldHaveSingleItem(); @@ -49,7 +55,7 @@ public async Task return_single_event() { public async Task max_count_is_respected() { var maxCount = Fixture.ExpectedEvents.Length / 2; var result = await Fixture.Streams - .ReadAllAsync(Direction.Backwards, Position.End, maxCount) + .ReadAllAsync(new ReadAllOptions().FromEnd().Max(maxCount)) .Take(Fixture.ExpectedEvents.Length) .ToArrayAsync(); @@ -59,7 +65,7 @@ public async Task max_count_is_respected() { [Fact] public async Task stream_found() { var count = await Fixture.Streams - .ReadAllAsync(Direction.Backwards, Position.End) + .ReadAllAsync(new ReadAllOptions().FromEnd()) .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) .CountAsync(); @@ -69,7 +75,14 @@ public async Task stream_found() { [Fact] public async Task with_timeout_fails_when_operation_expired() { var ex = await Fixture.Streams - .ReadAllAsync(Direction.Backwards, Position.Start, 1, false, TimeSpan.Zero) + .ReadAllAsync( + new ReadAllOptions { + Direction = Direction.Backwards, + Position = Position.Start, + MaxCount = 1, + Deadline = TimeSpan.Zero + } + ) .ToArrayAsync() .AsTask().ShouldThrowAsync(); @@ -79,7 +92,11 @@ public async Task with_timeout_fails_when_operation_expired() { [Fact] public async Task filter_events_by_type() { var result = await Fixture.Streams - .ReadAllAsync(Direction.Backwards, Position.End, EventTypeFilter.Prefix(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix)) + .ReadAllAsync( + new ReadAllOptions().FromEnd().WithFilter( + EventTypeFilter.Prefix(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix) + ) + ) .ToListAsync(); result.ForEach(x => x.Event.EventType.ShouldStartWith(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix)); diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsFixture.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsFixture.cs index dcb2fe1d3..3d78be13b 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsFixture.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsFixture.cs @@ -14,7 +14,7 @@ public ReadAllEventsFixture() { SystemStreams.AllStream, StreamState.NoStream, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); Events = CreateTestEvents(20) @@ -36,11 +36,11 @@ public ReadAllEventsFixture() { public string ExpectedStreamName { get; private set; } = null!; - public EventData[] Events { get; private set; } = Array.Empty(); + public MessageData[] Events { get; private set; } = []; - public EventBinaryData[] ExpectedEvents { get; private set; } = Array.Empty(); - public EventBinaryData[] ExpectedEventsReversed { get; private set; } = Array.Empty(); + public MessageBinaryData[] ExpectedEvents { get; private set; } = []; + public MessageBinaryData[] ExpectedEventsReversed { get; private set; } = []; - public EventBinaryData ExpectedFirstEvent { get; private set; } - public EventBinaryData ExpectedLastEvent { get; private set; } + public MessageBinaryData ExpectedFirstEvent { get; private set; } + public MessageBinaryData ExpectedLastEvent { get; private set; } } diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsForwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsForwardTests.cs index 442292c15..2fe0fbc18 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsForwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadAllEventsForwardTests.cs @@ -9,17 +9,25 @@ namespace KurrentDB.Client.Tests; [Trait("Category", "Operation:Read")] [Trait("Category", "Operation:Read:Forwards")] [Trait("Category", "Database:Dedicated")] -public class ReadAllEventsForwardTests(ITestOutputHelper output, ReadAllEventsFixture fixture) : KurrentTemporaryTests(output, fixture) { +public class ReadAllEventsForwardTests(ITestOutputHelper output, ReadAllEventsFixture fixture) + : KurrentTemporaryTests(output, fixture) { [Fact] public async Task return_empty_if_reading_from_end() { - var result = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.End, 1).CountAsync(); + var result = await Fixture.Streams.ReadAllAsync( + new ReadAllOptions { + Direction = Direction.Forwards, + Position = Position.End, + MaxCount = 1 + } + ).CountAsync(); + result.ShouldBe(0); } [Fact] public async Task return_partial_slice_if_not_enough_events() { var sliceSize = Fixture.ExpectedEvents.Length * 2; - var result = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start, sliceSize).ToArrayAsync(); + var result = await Fixture.Streams.ReadAllAsync(new ReadAllOptions { MaxCount = sliceSize }).ToArrayAsync(); result.Length.ShouldBeLessThan(sliceSize); } @@ -27,7 +35,7 @@ public async Task return_partial_slice_if_not_enough_events() { public async Task return_events_in_correct_order_compared_to_written() { // TODO SS: this test must be fixed to deterministically read the expected events regardless of how many already exist. reading all for now... var result = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start) + .ReadAllAsync() .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) .ToBinaryData(); @@ -37,7 +45,7 @@ public async Task return_events_in_correct_order_compared_to_written() { [Fact] public async Task return_single_event() { var result = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, 1) + .ReadAllAsync(new ReadAllOptions { MaxCount = 1 }) .ToArrayAsync(); result.ShouldHaveSingleItem(); @@ -47,7 +55,7 @@ public async Task return_single_event() { public async Task max_count_is_respected() { var maxCount = Fixture.ExpectedEvents.Length / 2; var result = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, maxCount) + .ReadAllAsync(new ReadAllOptions { MaxCount = maxCount }) .Take(Fixture.ExpectedEvents.Length) .ToArrayAsync(); @@ -57,7 +65,7 @@ public async Task max_count_is_respected() { [Fact] public async Task reads_all_events_by_default() { var count = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start) + .ReadAllAsync() .CountAsync(); Assert.True(count >= Fixture.ExpectedEvents.Length); @@ -66,7 +74,7 @@ public async Task reads_all_events_by_default() { [Fact] public async Task stream_found() { var count = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start) + .ReadAllAsync() .Where(x => x.OriginalStreamId == Fixture.ExpectedStreamName) .CountAsync(); @@ -90,22 +98,20 @@ await Fixture.Streams.SetStreamMetadataAsync( await Fixture.Streams.AppendToStreamAsync( linkedStream, StreamState.Any, - new[] { - new EventData( - Uuid.NewUuid(), + [ + new MessageData( SystemEventTypes.LinkTo, Encoding.UTF8.GetBytes($"0@{deletedStream}"), Array.Empty(), + Uuid.NewUuid(), Constants.Metadata.ContentTypes.ApplicationOctetStream ) - } + ] ); var events = await Fixture.Streams.ReadStreamAsync( - Direction.Forwards, linkedStream, - StreamPosition.Start, - resolveLinkTos: true + new ReadStreamOptions { ResolveLinkTos = true } ) .ToArrayAsync(); @@ -115,10 +121,7 @@ await Fixture.Streams.AppendToStreamAsync( [Fact] public async Task enumeration_all_referencing_messages_twice_does_not_throw() { var result = Fixture.Streams.ReadAllAsync( - Direction.Forwards, - Position.Start, - 32, - userCredentials: TestCredentials.Root + new ReadAllOptions { MaxCount = 32, UserCredentials = TestCredentials.Root } ); _ = result.Messages; @@ -128,10 +131,7 @@ public async Task enumeration_all_referencing_messages_twice_does_not_throw() { [Fact] public async Task enumeration_all_enumerating_messages_twice_throws() { var result = Fixture.Streams.ReadAllAsync( - Direction.Forwards, - Position.Start, - 32, - userCredentials: TestCredentials.Root + new ReadAllOptions { MaxCount = 32, UserCredentials = TestCredentials.Root } ); await result.Messages.ToArrayAsync(); @@ -145,7 +145,12 @@ await result.Messages.ToArrayAsync() [Fact] public async Task filter_events_by_type() { var result = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, EventTypeFilter.Prefix(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix)) + .ReadAllAsync( + new ReadAllOptions + { + Filter = EventTypeFilter.Prefix(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix) + } + ) .ToListAsync(); result.ForEach(x => x.Event.EventType.ShouldStartWith(KurrentDBTemporaryFixture.AnotherTestEventTypePrefix)); diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs index 1fd9d1d9e..8f803ae04 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamBackwardTests.cs @@ -17,11 +17,18 @@ public async Task count_le_equal_zero_throws(long maxCount) { var ex = await Assert.ThrowsAsync( () => - Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.Start, maxCount) + Fixture.Streams.ReadStreamAsync( + stream, + new ReadStreamOptions { + Direction = Direction.Backwards, + StreamPosition = StreamPosition.Start, + MaxCount = maxCount + } + ) .ToArrayAsync().AsTask() ); - Assert.Equal(nameof(maxCount), ex.ParamName); + Assert.Equal(nameof(ReadStreamOptions.MaxCount), ex.ParamName); } [Fact] @@ -30,7 +37,7 @@ public async Task stream_does_not_exist_throws() { var ex = await Assert.ThrowsAsync( () => Fixture.Streams - .ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) + .ReadStreamAsync(stream, new ReadStreamOptions().Last()) .ToArrayAsync().AsTask() ); @@ -41,7 +48,7 @@ public async Task stream_does_not_exist_throws() { public async Task stream_does_not_exist_can_be_checked() { var stream = Fixture.GetStreamName(); - var result = Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1); + var result = Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().Last()); var state = await result.ReadState; Assert.Equal(ReadState.StreamNotFound, state); @@ -55,7 +62,7 @@ public async Task stream_deleted_throws() { var ex = await Assert.ThrowsAsync( () => Fixture.Streams - .ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) + .ReadStreamAsync(stream, new ReadStreamOptions().Last()) .ToArrayAsync().AsTask() ); @@ -73,11 +80,11 @@ public async Task returns_events_in_reversed_order(string suffix, int count, int await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); var actual = await Fixture.Streams - .ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, expected.Length) + .ReadStreamAsync(stream, new ReadStreamOptions().FromEnd().Max(expected.Length)) .Select(x => x.Event).ToArrayAsync(); Assert.True( - EventDataComparer.Equal( + MessageDataComparer.Equal( Enumerable.Reverse(expected).ToArray(), actual ) @@ -93,11 +100,12 @@ public async Task be_able_to_read_single_event_from_arbitrary_position() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, new(7), 1) + var actual = await Fixture.Streams + .ReadStreamAsync(stream, new ReadStreamOptions().From(new(7)).Backwards().MaxOne()) .Select(x => x.Event) .SingleAsync(); - Assert.True(EventDataComparer.Equal(expected, actual)); + Assert.True(MessageDataComparer.Equal(expected, actual)); } [Fact] @@ -108,11 +116,12 @@ public async Task be_able_to_read_from_arbitrary_position() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, new(3), 2) + var actual = await Fixture.Streams + .ReadStreamAsync(stream, new ReadStreamOptions().From(new(3)).Backwards().Max(2)) .Select(x => x.Event) .ToArrayAsync(); - Assert.True(EventDataComparer.Equal(events.Skip(2).Take(2).Reverse().ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(events.Skip(2).Take(2).Reverse().ToArray(), actual)); } [Fact] @@ -123,12 +132,13 @@ public async Task be_able_to_read_first_event() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); - var events = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.Start, 1) + var events = await Fixture.Streams + .ReadStreamAsync(stream, new ReadStreamOptions().Backwards().FromStart().Max(1)) .Select(x => x.Event) .ToArrayAsync(); Assert.Single(events); - Assert.True(EventDataComparer.Equal(testEvents[0], events[0])); + Assert.True(MessageDataComparer.Equal(testEvents[0], events[0])); } [Fact] @@ -138,12 +148,13 @@ public async Task be_able_to_read_last_event() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); - var events = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) + var events = await Fixture.Streams + .ReadStreamAsync(stream, new ReadStreamOptions().Last()) .Select(x => x.Event) .ToArrayAsync(); Assert.Single(events); - Assert.True(EventDataComparer.Equal(testEvents[^1], events[0])); + Assert.True(MessageDataComparer.Equal(testEvents[^1], events[0])); } [Fact] @@ -160,7 +171,7 @@ await Fixture.Streams.AppendToStreamAsync( ); var events = await Fixture.Streams - .ReadStreamAsync(Direction.Backwards, streamName, StreamPosition.End, maxCount) + .ReadStreamAsync(streamName, new ReadStreamOptions().FromEnd().Max(maxCount)) .Take(count) .ToArrayAsync(); @@ -177,7 +188,8 @@ public async Task populates_log_position_of_event() { var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 1) + var actual = await Fixture.Streams + .ReadStreamAsync(stream, new ReadStreamOptions().Last()) .Select(x => x.Event) .ToArrayAsync(); @@ -194,14 +206,7 @@ public async Task with_timeout_fails_when_operation_expired() { var rpcException = await Assert.ThrowsAsync( () => Fixture.Streams - .ReadStreamAsync( - Direction.Backwards, - stream, - StreamPosition.End, - 1, - false, - TimeSpan.Zero - ) + .ReadStreamAsync(stream, new ReadStreamOptions { Deadline = TimeSpan.Zero }.Last()) .ToArrayAsync().AsTask() ); @@ -211,11 +216,8 @@ public async Task with_timeout_fails_when_operation_expired() { [Fact] public async Task enumeration_referencing_messages_twice_does_not_throw() { var result = Fixture.Streams.ReadStreamAsync( - Direction.Forwards, "$dbUsers", - StreamPosition.Start, - 32, - userCredentials: TestCredentials.Root + new ReadStreamOptions { UserCredentials = TestCredentials.Root, MaxCount = 32 } ); _ = result.Messages; @@ -225,11 +227,8 @@ public async Task enumeration_referencing_messages_twice_does_not_throw() { [Fact] public async Task enumeration_enumerating_messages_twice_throws() { var result = Fixture.Streams.ReadStreamAsync( - Direction.Forwards, "$dbUsers", - StreamPosition.Start, - 32, - userCredentials: TestCredentials.Root + new ReadStreamOptions { UserCredentials = TestCredentials.Root, MaxCount = 32 } ); await result.Messages.ToArrayAsync(); @@ -242,11 +241,9 @@ await result.Messages.ToArrayAsync() [Fact] public async Task stream_not_found() { - var result = await Fixture.Streams.ReadStreamAsync( - Direction.Backwards, - Fixture.GetStreamName(), - StreamPosition.End - ).Messages.SingleAsync(); + var result = await Fixture.Streams + .ReadStreamAsync(Fixture.GetStreamName(), new ReadStreamOptions().FromEnd()).Messages + .SingleAsync(); Assert.Equal(StreamMessage.NotFound.Instance, result); } @@ -262,9 +259,8 @@ public async Task stream_found() { await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, events); var result = await Fixture.Streams.ReadStreamAsync( - Direction.Backwards, streamName, - StreamPosition.End + new ReadStreamOptions().FromEnd() ).Messages.ToArrayAsync(); Assert.Equal( diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamEventsLinkedToDeletedStreamTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamEventsLinkedToDeletedStreamTests.cs index f852121f0..9d853880a 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamEventsLinkedToDeletedStreamTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamEventsLinkedToDeletedStreamTests.cs @@ -27,14 +27,16 @@ public abstract class ReadStreamEventsLinkedToDeletedStreamTests(ReadEventsLinke [UsedImplicitly] [Trait("Category", "Operation:Read")] [Trait("Category", "Operation:Read:Forwards")] - public class Forwards(Forwards.CustomFixture fixture) : ReadStreamEventsLinkedToDeletedStreamTests(fixture), IClassFixture { + public class Forwards(Forwards.CustomFixture fixture) : ReadStreamEventsLinkedToDeletedStreamTests(fixture), + IClassFixture { public class CustomFixture() : ReadEventsLinkedToDeletedStreamFixture(Direction.Forwards); } [UsedImplicitly] [Trait("Category", "Operation:Read")] [Trait("Category", "Operation:Read:Backwards")] - public class Backwards(Backwards.CustomFixture fixture) : ReadStreamEventsLinkedToDeletedStreamTests(fixture), IClassFixture { + public class Backwards(Backwards.CustomFixture fixture) : ReadStreamEventsLinkedToDeletedStreamTests(fixture), + IClassFixture { public class CustomFixture() : ReadEventsLinkedToDeletedStreamFixture(Direction.Backwards); } } @@ -50,25 +52,25 @@ protected ReadEventsLinkedToDeletedStreamFixture(Direction direction) { await Streams.AppendToStreamAsync( LinkedStream, StreamState.Any, - new[] { - new EventData( - Uuid.NewUuid(), + [ + new MessageData( SystemEventTypes.LinkTo, Encoding.UTF8.GetBytes($"0@{DeletedStream}"), Array.Empty(), - Constants.Metadata.ContentTypes.ApplicationOctetStream + contentType: Constants.Metadata.ContentTypes.ApplicationOctetStream ) - } + ] ); await Streams.DeleteAsync(DeletedStream, StreamState.Any); Events = await Streams.ReadStreamAsync( - direction, LinkedStream, - StreamPosition.Start, - 1, - true + new ReadStreamOptions { + Direction = direction, + MaxCount = 1, + ResolveLinkTos = true + } ).ToArrayAsync(); }; } diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs index 5457f9ddb..08bfc2753 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamForwardTests.cs @@ -1,12 +1,11 @@ -using KurrentDB.Client; - namespace KurrentDB.Client.Tests; [Trait("Category", "Target:Streams")] [Trait("Category", "Target:Streams")] [Trait("Category", "Operation:Read")] [Trait("Category", "Operation:Read:Forwards")] -public class ReadStreamForwardTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) : KurrentPermanentTests(output, fixture) { +public class ReadStreamForwardTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) + : KurrentPermanentTests(output, fixture) { [Theory] [InlineData(0)] public async Task count_le_equal_zero_throws(long maxCount) { @@ -14,11 +13,11 @@ public async Task count_le_equal_zero_throws(long maxCount) { var ex = await Assert.ThrowsAsync( () => - Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, maxCount) + Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().Max(maxCount)) .ToArrayAsync().AsTask() ); - Assert.Equal(nameof(maxCount), ex.ParamName); + Assert.Equal(nameof(ReadStreamOptions.MaxCount), ex.ParamName); } [Fact] @@ -27,7 +26,7 @@ public async Task stream_does_not_exist_throws() { var ex = await Assert.ThrowsAsync( () => Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) + .ReadStreamAsync(stream, new ReadStreamOptions().MaxOne()) .ToArrayAsync().AsTask() ); @@ -38,7 +37,7 @@ public async Task stream_does_not_exist_throws() { public async Task stream_does_not_exist_can_be_checked() { var stream = Fixture.GetStreamName(); - var result = Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1); + var result = Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().MaxOne()); var state = await result.ReadState; Assert.Equal(ReadState.StreamNotFound, state); @@ -52,7 +51,7 @@ public async Task stream_deleted_throws() { var ex = await Assert.ThrowsAsync( () => Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) + .ReadStreamAsync(stream, new ReadStreamOptions().MaxOne()) .ToArrayAsync().AsTask() ); @@ -70,10 +69,10 @@ public async Task returns_events_in_order(string suffix, int count, int metadata await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); var actual = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, expected.Length) + .ReadStreamAsync(stream, new ReadStreamOptions().Max(expected.Length)) .Select(x => x.Event).ToArrayAsync(); - Assert.True(EventDataComparer.Equal(expected, actual)); + Assert.True(MessageDataComparer.Equal(expected, actual)); } [Fact] @@ -86,11 +85,11 @@ public async Task be_able_to_read_single_event_from_arbitrary_position() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, new(7), 1) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().From(new(7)).MaxOne()) .Select(x => x.Event) .SingleAsync(); - Assert.True(EventDataComparer.Equal(expected, actual)); + Assert.True(MessageDataComparer.Equal(expected, actual)); } [Fact] @@ -101,11 +100,11 @@ public async Task be_able_to_read_from_arbitrary_position() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, new(3), 2) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().From(new(3)).Max(2)) .Select(x => x.Event) .ToArrayAsync(); - Assert.True(EventDataComparer.Equal(events.Skip(3).Take(2).ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(events.Skip(3).Take(2).ToArray(), actual)); } [Fact] @@ -116,12 +115,12 @@ public async Task be_able_to_read_first_event() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, testEvents); - var events = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) + var events = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().MaxOne()) .Select(x => x.Event) .ToArrayAsync(); Assert.Single(events); - Assert.True(EventDataComparer.Equal(testEvents[0], events[0])); + Assert.True(MessageDataComparer.Equal(testEvents[0], events[0])); } [Fact] @@ -136,7 +135,7 @@ await Fixture.Streams.AppendToStreamAsync( Fixture.CreateTestEvents(count) ); - var events = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start, maxCount) + var events = await Fixture.Streams.ReadStreamAsync(streamName, new ReadStreamOptions().Max(maxCount)) .Take(count) .ToArrayAsync(); @@ -154,7 +153,7 @@ await Fixture.Streams.AppendToStreamAsync( ); var count = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start) + .ReadStreamAsync(streamName) .CountAsync(); Assert.True(count == maxCount); @@ -171,7 +170,7 @@ public async Task populates_log_position_of_event() { var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, events); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 1) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions().MaxOne()) .Select(x => x.Event) .ToArrayAsync(); @@ -182,11 +181,7 @@ public async Task populates_log_position_of_event() { [Fact] public async Task stream_not_found() { - var result = await Fixture.Streams.ReadStreamAsync( - Direction.Forwards, - Fixture.GetStreamName(), - StreamPosition.Start - ).Messages.SingleAsync(); + var result = await Fixture.Streams.ReadStreamAsync(Fixture.GetStreamName()).Messages.SingleAsync(); Assert.Equal(StreamMessage.NotFound.Instance, result); } @@ -201,11 +196,7 @@ public async Task stream_found() { await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, events); - var result = await Fixture.Streams.ReadStreamAsync( - Direction.Forwards, - streamName, - StreamPosition.Start - ).Messages.ToArrayAsync(); + var result = await Fixture.Streams.ReadStreamAsync(streamName).Messages.ToArrayAsync(); Assert.Equal( eventCount + (Fixture.EventStoreHasLastStreamPosition ? 2 : 1), @@ -242,11 +233,7 @@ await Fixture.Streams.SetStreamMetadataAsync( new(truncateBefore: new StreamPosition(32)) ); - var result = await Fixture.Streams.ReadStreamAsync( - Direction.Forwards, - streamName, - StreamPosition.Start - ).Messages.ToArrayAsync(); + var result = await Fixture.Streams.ReadStreamAsync(streamName).Messages.ToArrayAsync(); Assert.Equal( eventCount - 32 + (Fixture.EventStoreHasLastStreamPosition ? 3 : 1), diff --git a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamWhenHavingMaxCountSetForStreamTests.cs b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamWhenHavingMaxCountSetForStreamTests.cs index de1b7eafc..b5b006b56 100644 --- a/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamWhenHavingMaxCountSetForStreamTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Read/ReadStreamWhenHavingMaxCountSetForStreamTests.cs @@ -19,12 +19,12 @@ public async Task read_stream_forwards_respects_max_count() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 100) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = 100 }) .Select(x => x.Event) .ToArrayAsync(); Assert.Equal(3, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(2).ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(expected.Skip(2).ToArray(), actual)); } [Fact] @@ -37,12 +37,19 @@ public async Task read_stream_backwards_respects_max_count() { await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 100) + var actual = await Fixture.Streams.ReadStreamAsync( + stream, + new ReadStreamOptions { + Direction = Direction.Backwards, + StreamPosition = StreamPosition.End, + MaxCount = 100 + } + ) .Select(x => x.Event) .ToArrayAsync(); Assert.Equal(3, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(2).Reverse().ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(expected.Skip(2).Reverse().ToArray(), actual)); } [Fact] @@ -57,12 +64,12 @@ public async Task after_setting_less_strict_max_count_read_stream_forward_reads_ await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.StreamRevision(0), new(4)); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 100) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = 100 }) .Select(x => x.Event) .ToArrayAsync(); Assert.Equal(4, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(1).ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(expected.Skip(1).ToArray(), actual)); } [Fact] @@ -77,12 +84,12 @@ public async Task after_setting_more_strict_max_count_read_stream_forward_reads_ await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.StreamRevision(0), new(2)); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start, 100) + var actual = await Fixture.Streams.ReadStreamAsync(stream, new ReadStreamOptions { MaxCount = 100 }) .Select(x => x.Event) .ToArrayAsync(); Assert.Equal(2, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(3).ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(expected.Skip(3).ToArray(), actual)); } [Fact] @@ -97,12 +104,19 @@ public async Task after_setting_less_strict_max_count_read_stream_backwards_read await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.StreamRevision(0), new(4)); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 100) + var actual = await Fixture.Streams.ReadStreamAsync( + stream, + new ReadStreamOptions { + Direction = Direction.Backwards, + StreamPosition = StreamPosition.End, + MaxCount = 100 + } + ) .Select(x => x.Event) .ToArrayAsync(); Assert.Equal(4, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(1).Reverse().ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(expected.Skip(1).Reverse().ToArray(), actual)); } [Fact] @@ -117,11 +131,18 @@ public async Task after_setting_more_strict_max_count_read_stream_backwards_read await Fixture.Streams.SetStreamMetadataAsync(stream, StreamState.StreamRevision(0), new(2)); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Backwards, stream, StreamPosition.End, 100) + var actual = await Fixture.Streams.ReadStreamAsync( + stream, + new ReadStreamOptions { + Direction = Direction.Backwards, + StreamPosition = StreamPosition.End, + MaxCount = 100 + } + ) .Select(x => x.Event) .ToArrayAsync(); Assert.Equal(2, actual.Length); - Assert.True(EventDataComparer.Equal(expected.Skip(3).Reverse().ToArray(), actual)); + Assert.True(MessageDataComparer.Equal(expected.Skip(3).Reverse().ToArray(), actual)); } } diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs index 2d3223d18..013c717df 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.PersistentSubscriptions.cs @@ -18,7 +18,7 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s List expected = GenerateMessages(); //When - await Fixture.Streams.AppendToStreamAsync(stream, expected); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); var group = await CreateToStreamSubscription(stream); @@ -31,9 +31,11 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s public async Task message_data_and_metadata_are_serialized_and_deserialized_using_auto_serialization_with_registered_metadata() { // Given - await using var client = NewClientWith(serialization => serialization.UseMetadataType()); + await using var client = + NewClientWith(serialization => serialization.MessageTypeMapping.UseMetadataType()); + await using var subscriptionsClient = - NewSubscriptionsClientWith(serialization => serialization.UseMetadataType()); + NewSubscriptionsClientWith(serialization => serialization.MessageTypeMapping.UseMetadataType()); var stream = Fixture.GetStreamName(); var metadata = new CustomMetadata(Guid.NewGuid()); @@ -42,7 +44,7 @@ public async Task expected.Select(message => Message.From(message, metadata, Uuid.NewUuid())).ToList(); // When - await client.AppendToStreamAsync(stream, messagesWithMetadata); + await client.AppendToStreamAsync(stream, StreamState.NoStream, messagesWithMetadata); // Then var group = await CreateToStreamSubscription(stream, subscriptionsClient); @@ -62,10 +64,10 @@ public async Task expected.Select(message => Message.From(message, metadata, Uuid.NewUuid())).ToList(); // When - await Fixture.Streams.AppendToStreamAsync(stream, messagesWithMetadata); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, messagesWithMetadata); // Then - var group = await CreateToStreamSubscription(stream); + var group = await CreateToStreamSubscription(stream); var resolvedEvents = await Fixture.Subscriptions.SubscribeToStream(stream, group).Take(2) .ToListAsync(); @@ -80,9 +82,16 @@ public async Task subscribe_to_stream_without_options_does_NOT_deserialize_resol var (stream, expected) = await AppendEventsUsingAutoSerialization(); // When - var group = await CreateToStreamSubscription(stream); + var group = await CreateToStreamSubscription(stream); +#pragma warning disable CS0618 // Type or member is obsolete var resolvedEvents = await Fixture.Subscriptions - .SubscribeToStream(stream, group, int.MaxValue).Take(2) + .SubscribeToStream( + stream, + group, + bufferSize: int.MaxValue, + userCredentials: TestCredentials.Root + ).Take(2) +#pragma warning restore CS0618 // Type or member is obsolete .ToListAsync(); // Then @@ -95,9 +104,14 @@ public async Task subscribe_to_all_without_options_does_NOT_deserialize_resolved var (stream, expected) = await AppendEventsUsingAutoSerialization(); // When - var group = await CreateToAllSubscription(stream); + var group = await CreateToAllSubscription(stream); +#pragma warning disable CS0618 // Type or member is obsolete var resolvedEvents = await Fixture.Subscriptions - .SubscribeToAll(group, int.MaxValue) + .SubscribeToAll( + group, + int.MaxValue + ) +#pragma warning restore CS0618 // Type or member is obsolete .Take(2) .ToListAsync(); @@ -108,11 +122,11 @@ public async Task subscribe_to_all_without_options_does_NOT_deserialize_resolved public static TheoryData> CustomTypeMappings() { return [ (settings, typeName) => - settings.RegisterMessageType(typeName), + settings.MessageTypeMapping.Register(typeName), (settings, typeName) => - settings.RegisterMessageType(typeof(UserRegistered), typeName), + settings.MessageTypeMapping.Register(typeName, typeof(UserRegistered)), (settings, typeName) => - settings.RegisterMessageTypes(new Dictionary { { typeof(UserRegistered), typeName } }) + settings.MessageTypeMapping.Register(new Dictionary { { typeName, typeof(UserRegistered) } }) ]; } @@ -130,7 +144,7 @@ Action customTypeMapping var (stream, expected) = await AppendEventsUsingAutoSerialization(client); // Then - var group = await CreateToStreamSubscription(stream, subscriptionsClient); + var group = await CreateToStreamSubscription(stream, subscriptionsClient); var resolvedEvents = await subscriptionsClient.SubscribeToStream(stream, group).Take(2) .ToListAsync(); @@ -153,7 +167,7 @@ Action customTypeMapping var (stream, expected) = await AppendEventsUsingAutoSerialization(client); // Then - var group = await CreateToAllSubscription(stream, subscriptionsClient); + var group = await CreateToAllSubscription(stream, subscriptionsClient); var resolvedEvents = await subscriptionsClient .SubscribeToAll(group) .Where(r => r.Event.EventStreamId == stream) @@ -180,7 +194,7 @@ public async Task automatic_serialization_custom_json_settings_are_applied() { var (stream, expected) = await AppendEventsUsingAutoSerialization(client); // Then - var group = await CreateToStreamSubscription(stream, subscriptionsClient); + var group = await CreateToStreamSubscription(stream, subscriptionsClient); var resolvedEvents = await subscriptionsClient.SubscribeToStream(stream, group).Take(2) .ToListAsync(); @@ -197,22 +211,22 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, out string? typeName) { #else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, [NotNullWhen(true)] out string? typeName) { #endif - var typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; - type = Type.GetType(typeName); + var messageTypeName = record.EventType; + typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; - return type != null; + return true; } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, out string? typeName) { #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, [NotNullWhen(true)] out string? typeName) { #endif - type = null; + typeName = null; return false; } } @@ -233,8 +247,8 @@ public async Task append_and_subscribe_to_stream_uses_custom_message_type_naming var (stream, expected) = await AppendEventsUsingAutoSerialization(client); //Then - var group = await CreateToStreamSubscription(stream, subscriptionsClient); - var resolvedEvents = await subscriptionsClient.SubscribeToStream(stream,group).Take(2) + var group = await CreateToStreamSubscription(stream, subscriptionsClient); + var resolvedEvents = await subscriptionsClient.SubscribeToStream(stream, group).Take(2) .ToListAsync(); Assert.All( @@ -261,7 +275,7 @@ public async Task append_and_subscribe_to_all_uses_custom_message_type_naming_st var (stream, expected) = await AppendEventsUsingAutoSerialization(client); //Then - var group = await CreateToAllSubscription(stream, subscriptionsClient); + var group = await CreateToAllSubscription(stream, subscriptionsClient); var resolvedEvents = await subscriptionsClient .SubscribeToAll(group) .Where(r => r.Event.EventStreamId == stream) @@ -285,7 +299,7 @@ public async Task ); // When - var group = await CreateToStreamSubscription(stream); + var group = await CreateToStreamSubscription(stream); var resolvedEvents = await Fixture.Subscriptions.SubscribeToStream(stream, group).Take(2) .ToListAsync(); @@ -301,7 +315,7 @@ public async Task subscribe_to_all_deserializes_resolved_message_appended_with_m ); // When - var group = await CreateToAllSubscription(stream); + var group = await CreateToAllSubscription(stream); var resolvedEvents = await Fixture.Subscriptions .SubscribeToAll(group) .Where(r => r.Event.EventStreamId == stream) @@ -319,7 +333,7 @@ public async Task var (stream, expected) = await AppendEventsUsingManualSerialization(_ => "user_registered"); // When - var group = await CreateToStreamSubscription(stream); + var group = await CreateToStreamSubscription(stream); var resolvedEvents = await Fixture.Subscriptions.SubscribeToStream(stream, group).Take(2) .ToListAsync(); @@ -334,7 +348,7 @@ public async Task var (stream, expected) = await AppendEventsUsingManualSerialization(_ => "user_registered"); // When - var group = await CreateToAllSubscription(stream); + var group = await CreateToAllSubscription(stream); var resolvedEvents = await Fixture.Subscriptions .SubscribeToAll(group) .Where(r => r.Event.EventStreamId == stream) @@ -375,11 +389,15 @@ static void AreNotDeserialized(UserRegistered expected, ResolvedEvent resolvedEv ); } - async Task<(string, List)> AppendEventsUsingAutoSerialization(KurrentDBClient? kurrentDbClient = null) { + async Task<(string, List)> AppendEventsUsingAutoSerialization( + KurrentDBClient? kurrentDbClient = null + ) { var stream = Fixture.GetStreamName(); var messages = GenerateMessages(); - var writeResult = await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, messages); + var writeResult = + await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, StreamState.Any, messages); + Assert.Equal((ulong)messages.Count - 1, writeResult.NextExpectedStreamState); return (stream, messages); @@ -392,8 +410,7 @@ Func getTypeName var messages = GenerateMessages(); var eventData = messages.Select( message => - new EventData( - Uuid.NewUuid(), + new MessageData( getTypeName(message), Encoding.UTF8.GetBytes( JsonSerializer.Serialize( @@ -437,7 +454,9 @@ Action customizeSerialization return new KurrentDBPersistentSubscriptionsClient(settings); } - async Task CreateToStreamSubscription(string stream, KurrentDBPersistentSubscriptionsClient? client = null) { + async Task CreateToStreamSubscription( + string stream, KurrentDBPersistentSubscriptionsClient? client = null + ) { string group = Fixture.GetGroupName(); await (client ?? Fixture.Subscriptions).CreateToStreamAsync( @@ -455,7 +474,7 @@ async Task CreateToAllSubscription(string stream, KurrentDBPersistentSub await (client ?? Fixture.Subscriptions).CreateToAllAsync( group, - StreamFilter.Prefix(stream), + StreamFilter.Prefix(stream), new(startFrom: Position.Start), userCredentials: TestCredentials.Root ); diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs index 8092c44d3..8070b03aa 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.Subscriptions.cs @@ -1,7 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json; -using KurrentDB.Client; using KurrentDB.Client.Core.Serialization; using Kurrent.Diagnostics.Tracing; @@ -18,7 +17,7 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s List expected = GenerateMessages(); //When - await Fixture.Streams.AppendToStreamAsync(stream, expected); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); //Then var resolvedEvents = await Fixture.Streams.SubscribeToStream(stream).Take(2).ToListAsync(); @@ -29,7 +28,10 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s public async Task message_data_and_metadata_are_serialized_and_deserialized_using_auto_serialization_with_registered_metadata() { // Given - await using var client = NewClientWith(serialization => serialization.UseMetadataType()); + await using var client = NewClientWith( + serialization => + serialization.MessageTypeMapping.UseMetadataType() + ); var stream = Fixture.GetStreamName(); var metadata = new CustomMetadata(Guid.NewGuid()); @@ -38,7 +40,7 @@ public async Task expected.Select(message => Message.From(message, metadata, Uuid.NewUuid())).ToList(); // When - await client.AppendToStreamAsync(stream, messagesWithMetadata); + await client.AppendToStreamAsync(stream, StreamState.NoStream, messagesWithMetadata); // Then var resolvedEvents = await client.SubscribeToStream(stream).Take(2).ToListAsync(); @@ -57,7 +59,7 @@ public async Task expected.Select(message => Message.From(message, metadata, Uuid.NewUuid())).ToList(); // When - await Fixture.Streams.AppendToStreamAsync(stream, messagesWithMetadata); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, messagesWithMetadata); // Then var resolvedEvents = await Fixture.Streams.SubscribeToStream(stream).Take(2).ToListAsync(); @@ -72,8 +74,10 @@ public async Task subscribe_to_stream_without_options_does_NOT_deserialize_resol var (stream, expected) = await AppendEventsUsingAutoSerialization(); // When +#pragma warning disable CS0618 // Type or member is obsolete var resolvedEvents = await Fixture.Streams .SubscribeToStream(stream, FromStream.Start).Take(2) +#pragma warning restore CS0618 // Type or member is obsolete .ToListAsync(); // Then @@ -86,8 +90,10 @@ public async Task subscribe_to_all_without_options_does_NOT_deserialize_resolved var (stream, expected) = await AppendEventsUsingAutoSerialization(); // When +#pragma warning disable CS0618 // Type or member is obsolete var resolvedEvents = await Fixture.Streams .SubscribeToAll(FromAll.Start, filterOptions: new SubscriptionFilterOptions(StreamFilter.Prefix(stream))) +#pragma warning restore CS0618 // Type or member is obsolete .Take(2) .ToListAsync(); @@ -98,11 +104,13 @@ public async Task subscribe_to_all_without_options_does_NOT_deserialize_resolved public static TheoryData> CustomTypeMappings() { return [ (settings, typeName) => - settings.RegisterMessageType(typeName), + settings.MessageTypeMapping.Register(typeName), (settings, typeName) => - settings.RegisterMessageType(typeof(UserRegistered), typeName), + settings.MessageTypeMapping.Register(typeName, typeof(UserRegistered)), (settings, typeName) => - settings.RegisterMessageTypes(new Dictionary { { typeof(UserRegistered), typeName } }) + settings.MessageTypeMapping.Register( + new Dictionary { { typeName, typeof(UserRegistered) } } + ) ]; } @@ -172,20 +180,20 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, out string? typeName) { #else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, [NotNullWhen(true)] out string? typeName) { #endif - var typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; - type = Type.GetType(typeName); + var messageTypeName = record.EventType; + typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; - return type != null; + return true; } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, out string? type) { #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, [NotNullWhen(true)] out string? type) { #endif type = null; return false; @@ -267,7 +275,8 @@ public async Task subscribe_to_all_deserializes_resolved_message_appended_with_m } [RetryFact] - public async Task subscribe_to_stream_does_NOT_deserialize_resolved_message_appended_with_manual_incompatible_serialization() { + public async Task + subscribe_to_stream_does_NOT_deserialize_resolved_message_appended_with_manual_incompatible_serialization() { // Given var (stream, expected) = await AppendEventsUsingManualSerialization(_ => "user_registered"); @@ -323,11 +332,15 @@ static void AreNotDeserialized(UserRegistered expected, ResolvedEvent resolvedEv ); } - async Task<(string, List)> AppendEventsUsingAutoSerialization(KurrentDBClient? kurrentDbClient = null) { + async Task<(string, List)> AppendEventsUsingAutoSerialization( + KurrentDBClient? kurrentDbClient = null + ) { var stream = Fixture.GetStreamName(); var messages = GenerateMessages(); - var writeResult = await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, messages); + var writeResult = + await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, StreamState.Any, messages); + Assert.Equal((ulong)messages.Count - 1, writeResult.NextExpectedStreamState); return (stream, messages); @@ -340,8 +353,7 @@ Func getTypeName var messages = GenerateMessages(); var eventData = messages.Select( message => - new EventData( - Uuid.NewUuid(), + new MessageData( getTypeName(message), Encoding.UTF8.GetBytes( JsonSerializer.Serialize( diff --git a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs index edc0be143..9330785e0 100644 --- a/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Serialization/SerializationTests.cs @@ -18,18 +18,45 @@ public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_s List expected = GenerateMessages(); //When - await Fixture.Streams.AppendToStreamAsync(stream, expected); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, expected); //Then var resolvedEvents = await Fixture.Streams.ReadStreamAsync(stream).ToListAsync(); - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); + } + + [RetryFact] + public async Task plain_clr_objects_are_serialized_and_deserialized_using_auto_serialization_and_extension() { + // Given + var stream = Fixture.GetStreamName(); + List domainMessages = GenerateMessages(); + List messages = domainMessages.Select(Message.From).ToList(); + + //When + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, messages); + + //Then + List deserializedMessages = await Fixture.Streams + .ReadStreamAsync(stream) + .DeserializedMessages() + .ToListAsync(); + + List deserializedDomainMessages = await Fixture.Streams + .ReadStreamAsync(stream) + .DeserializedData() + .ToListAsync(); + + Assert.Equal(messages, deserializedMessages); + Assert.Equal(domainMessages, deserializedDomainMessages); } [RetryFact] public async Task message_data_and_metadata_are_serialized_and_deserialized_using_auto_serialization_with_registered_metadata() { // Given - await using var client = NewClientWith(serialization => serialization.UseMetadataType()); + await using var client = NewClientWith( + serialization => serialization.MessageTypeMapping.UseMetadataType() + ); var stream = Fixture.GetStreamName(); var metadata = new CustomMetadata(Guid.NewGuid()); @@ -38,11 +65,11 @@ public async Task expected.Select(message => Message.From(message, metadata, Uuid.NewUuid())).ToList(); // When - await client.AppendToStreamAsync(stream, messagesWithMetadata); + await client.AppendToStreamAsync(stream, StreamState.NoStream, messagesWithMetadata); // Then var resolvedEvents = await client.ReadStreamAsync(stream).ToListAsync(); - var messages = AssertThatMessages(AreDeserialized, expected, resolvedEvents); + var messages = AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); Assert.Equal(messagesWithMetadata, messages); } @@ -57,11 +84,11 @@ public async Task expected.Select(message => Message.From(message, metadata, Uuid.NewUuid())).ToList(); // When - await Fixture.Streams.AppendToStreamAsync(stream, messagesWithMetadata); + await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, messagesWithMetadata); // Then var resolvedEvents = await Fixture.Streams.ReadStreamAsync(stream).ToListAsync(); - var messages = AssertThatMessages(AreDeserialized, expected, resolvedEvents); + var messages = AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); Assert.Equal(messagesWithMetadata.Select(m => m with { Metadata = new TracingMetadata() }), messages); } @@ -72,36 +99,64 @@ public async Task read_stream_without_options_does_NOT_deserialize_resolved_mess var (stream, expected) = await AppendEventsUsingAutoSerialization(); // When +#pragma warning disable CS0618 // Type or member is obsolete var resolvedEvents = await Fixture.Streams .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) +#pragma warning restore CS0618 // Type or member is obsolete .ToListAsync(); // Then - AssertThatMessages(AreNotDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreNotDeserialized, expected, resolvedEvents); } [RetryFact] - public async Task read_all_without_options_does_NOT_deserialize_resolved_message() { + public async Task read_stream_with_disabled_autoserialization_does_NOT_deserialize_resolved_message() { // Given var (stream, expected) = await AppendEventsUsingAutoSerialization(); // When var resolvedEvents = await Fixture.Streams - .ReadAllAsync(Direction.Forwards, Position.Start, StreamFilter.Prefix(stream)) + .ReadStreamAsync( + stream, + new ReadStreamOptions { + SerializationSettings = OperationSerializationSettings.Disabled + } + ) .ToListAsync(); // Then - AssertThatMessages(AreNotDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreNotDeserialized, expected, resolvedEvents); + } + + [RetryFact] + public async Task read_all_with_disabled_autoserialization_does_NOT_deserialize_resolved_message() { + // Given + var (stream, expected) = await AppendEventsUsingAutoSerialization(); + + // When + var resolvedEvents = await Fixture.Streams + .ReadAllAsync( + new ReadAllOptions { + Filter = StreamFilter.Prefix(stream), + SerializationSettings = OperationSerializationSettings.Disabled + } + ) + .ToListAsync(); + + // Then + AssertThatReadEvents(AreNotDeserialized, expected, resolvedEvents); } public static TheoryData> CustomTypeMappings() { return [ (settings, typeName) => - settings.RegisterMessageType(typeName), + settings.MessageTypeMapping.Register(typeName), (settings, typeName) => - settings.RegisterMessageType(typeof(UserRegistered), typeName), + settings.MessageTypeMapping.Register(typeName, typeof(UserRegistered)), (settings, typeName) => - settings.RegisterMessageTypes(new Dictionary { { typeof(UserRegistered), typeName } }) + settings.MessageTypeMapping.Register( + new Dictionary { { typeName, typeof(UserRegistered) } } + ) ]; } @@ -120,7 +175,7 @@ Action customTypeMapping var resolvedEvents = await client.ReadStreamAsync(stream).ToListAsync(); Assert.All(resolvedEvents, resolvedEvent => Assert.Equal("user_registered", resolvedEvent.Event.EventType)); - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } [RetryTheory] @@ -141,7 +196,7 @@ Action customTypeMapping Assert.All(resolvedEvents, resolvedEvent => Assert.Equal("user_registered", resolvedEvent.Event.EventType)); - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } [RetryFact] @@ -162,7 +217,7 @@ public async Task automatic_serialization_custom_json_settings_are_applied() { Assert.Equal(expected.Select(m => m.UserId), jsons.Select(j => j.GetProperty("user-id").GetGuid())); - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } public class CustomMessageTypeNamingStrategy : IMessageTypeNamingStrategy { @@ -171,20 +226,20 @@ public string ResolveTypeName(Type messageType, MessageTypeNamingResolutionConte } #if NET48 - public bool TryResolveClrType(string messageTypeName, out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, out string? typeName) { #else - public bool TryResolveClrType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrTypeName(EventRecord record, [NotNullWhen(true)] out string? typeName) { #endif - var typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; - type = Type.GetType(typeName); + var messageTypeName = record.EventType; + typeName = messageTypeName[(messageTypeName.IndexOf('-') + 1)..]; - return type != null; + return true; } #if NET48 - public bool TryResolveClrMetadataType(string messageTypeName, out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, out string? type) { #else - public bool TryResolveClrMetadataType(string messageTypeName, [NotNullWhen(true)] out Type? type) { + public bool TryResolveClrMetadataTypeName(EventRecord record, [NotNullWhen(true)] out string? type) { #endif type = null; return false; @@ -203,8 +258,12 @@ public async Task append_and_read_stream_uses_custom_message_type_naming_strateg //Then var resolvedEvents = await Fixture.Streams.ReadStreamAsync(stream).ToListAsync(); - Assert.All(resolvedEvents, resolvedEvent => Assert.Equal($"custom-{typeof(UserRegistered).FullName}", resolvedEvent.Event.EventType)); - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + Assert.All( + resolvedEvents, + resolvedEvent => Assert.Equal($"custom-{typeof(UserRegistered).FullName}", resolvedEvent.Event.EventType) + ); + + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } [RetryFact] @@ -222,8 +281,12 @@ public async Task append_and_read_all_uses_custom_message_type_naming_strategy() .ReadAllAsync(new ReadAllOptions { Filter = StreamFilter.Prefix(stream) }) .ToListAsync(); - Assert.All(resolvedEvents, resolvedEvent => Assert.Equal($"custom-{typeof(UserRegistered).FullName}", resolvedEvent.Event.EventType)); - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + Assert.All( + resolvedEvents, + resolvedEvent => Assert.Equal($"custom-{typeof(UserRegistered).FullName}", resolvedEvent.Event.EventType) + ); + + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } [RetryFact] @@ -239,7 +302,7 @@ public async Task read_stream_deserializes_resolved_message_appended_with_manual .ToListAsync(); // Then - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } [RetryFact] @@ -255,11 +318,12 @@ public async Task read_all_deserializes_resolved_message_appended_with_manual_co .ToListAsync(); // Then - AssertThatMessages(AreDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreDeserialized, expected, resolvedEvents); } [RetryFact] - public async Task read_stream_does_NOT_deserialize_resolved_message_appended_with_manual_incompatible_serialization() { + public async Task + read_stream_does_NOT_deserialize_resolved_message_appended_with_manual_incompatible_serialization() { // Given var (stream, expected) = await AppendEventsUsingManualSerialization(_ => "user_registered"); @@ -269,7 +333,7 @@ public async Task read_stream_does_NOT_deserialize_resolved_message_appended_wit .ToListAsync(); // Then - AssertThatMessages(AreNotDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreNotDeserialized, expected, resolvedEvents); } [RetryFact] @@ -283,10 +347,10 @@ public async Task read_all_does_NOT_deserialize_resolved_message_appended_with_m .ToListAsync(); // Then - AssertThatMessages(AreNotDeserialized, expected, resolvedEvents); + AssertThatReadEvents(AreNotDeserialized, expected, resolvedEvents); } - static List AssertThatMessages( + static List AssertThatReadEvents( Action assertMatches, List expected, List resolvedEvents @@ -316,11 +380,15 @@ static void AreNotDeserialized(UserRegistered expected, ResolvedEvent resolvedEv ); } - async Task<(string, List)> AppendEventsUsingAutoSerialization(KurrentDBClient? kurrentDbClient = null) { + async Task<(string, List)> AppendEventsUsingAutoSerialization( + KurrentDBClient? kurrentDbClient = null + ) { var stream = Fixture.GetStreamName(); var messages = GenerateMessages(); - var writeResult = await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, messages); + var writeResult = + await (kurrentDbClient ?? Fixture.Streams).AppendToStreamAsync(stream, StreamState.NoStream, messages); + Assert.Equal((ulong)messages.Count - 1, writeResult.NextExpectedStreamState); return (stream, messages); @@ -333,8 +401,7 @@ Func getTypeName var messages = GenerateMessages(); var eventData = messages.Select( message => - new EventData( - Uuid.NewUuid(), + new MessageData( getTypeName(message), Encoding.UTF8.GetBytes( JsonSerializer.Serialize( diff --git a/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs b/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs index 0510450c4..8c80afe61 100644 --- a/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/SoftDeleteTests.cs @@ -5,7 +5,8 @@ namespace KurrentDB.Client.Tests.Streams; [Trait("Category", "Target:Streams")] [Trait("Category", "Operation:Delete")] -public class SoftDeleteTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) : KurrentPermanentTests(output, fixture) { +public class SoftDeleteTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) + : KurrentPermanentTests(output, fixture) { static JsonDocument CustomMetadata { get; } static SoftDeleteTests() { @@ -33,7 +34,7 @@ public async Task reading_throws() { await Fixture.Streams.DeleteAsync(stream, writeResult.NextExpectedStreamState); await Assert.ThrowsAsync( - () => Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + () => Fixture.Streams.ReadStreamAsync(stream) .ToArrayAsync().AsTask() ); } @@ -60,12 +61,12 @@ public async Task recreated_with_any_expected_version(StreamState expectedState, await Task.Delay(50); //TODO: This is a workaround until github issue #1744 is fixed - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + var actual = await Fixture.Streams.ReadStreamAsync(stream) .Select(x => x.Event) .ToArrayAsync(); Assert.Equal(3, actual.Length); - Assert.Equal(events.Select(x => x.EventId), actual.Select(x => x.EventId)); + Assert.Equal(events.Select(x => x.MessageId), actual.Select(x => x.EventId)); Assert.Equal( Enumerable.Range(1, 3).Select(i => new StreamPosition((ulong)i)), actual.Select(x => x.EventNumber) @@ -102,12 +103,12 @@ public async Task recreated_with_expected_version() { await Task.Delay(50); //TODO: This is a workaround until github issue #1744 is fixed - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + var actual = await Fixture.Streams.ReadStreamAsync(stream) .Select(x => x.Event) .ToArrayAsync(); Assert.Equal(3, actual.Length); - Assert.Equal(events.Select(x => x.EventId), actual.Select(x => x.EventId)); + Assert.Equal(events.Select(x => x.MessageId), actual.Select(x => x.EventId)); Assert.Equal( Enumerable.Range(1, 3).Select(i => new StreamPosition((ulong)i)), actual.Select(x => x.EventNumber) @@ -153,12 +154,12 @@ public async Task recreated_preserves_metadata_except_truncate_before() { await Task.Delay(500); //TODO: This is a workaround until github issue #1744 is fixed - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + var actual = await Fixture.Streams.ReadStreamAsync(stream) .Select(x => x.Event) .ToArrayAsync(); Assert.Equal(3, actual.Length); - Assert.Equal(events.Select(x => x.EventId), actual.Select(x => x.EventId)); + Assert.Equal(events.Select(x => x.MessageId), actual.Select(x => x.EventId)); Assert.Equal( Enumerable.Range(count, 3).Select(i => new StreamPosition((ulong)i)), actual.Select(x => x.EventNumber) @@ -196,7 +197,7 @@ await Fixture.Streams.AppendToStreamAsync( await Fixture.Streams.TombstoneAsync(stream, StreamState.Any); var ex = await Assert.ThrowsAsync( - () => Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + () => Fixture.Streams.ReadStreamAsync(stream) .ToArrayAsync().AsTask() ); @@ -209,7 +210,9 @@ await Fixture.Streams.AppendToStreamAsync( Assert.Equal(SystemStreams.MetastreamOf(stream), ex.Stream); - await Assert.ThrowsAsync(() => Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents())); + await Assert.ThrowsAsync( + () => Fixture.Streams.AppendToStreamAsync(stream, StreamState.Any, Fixture.CreateTestEvents()) + ); } [Fact] @@ -247,7 +250,11 @@ await Assert.ThrowsAsync( public async Task allows_recreating_for_first_write_only_returns_wrong_expected_version() { var stream = Fixture.GetStreamName(); - var writeResult = await Fixture.Streams.AppendToStreamAsync(stream, StreamState.NoStream, Fixture.CreateTestEvents(2)); + var writeResult = await Fixture.Streams.AppendToStreamAsync( + stream, + StreamState.NoStream, + Fixture.CreateTestEvents(2) + ); Assert.Equal(new(1), writeResult.NextExpectedStreamState); @@ -265,7 +272,7 @@ public async Task allows_recreating_for_first_write_only_returns_wrong_expected_ stream, StreamState.NoStream, Fixture.CreateTestEvents(), - options => options.ThrowOnAppendFailure = false + new AppendToStreamOptions { ThrowOnAppendFailure = false } ); Assert.IsType(wrongExpectedVersionResult); @@ -297,11 +304,11 @@ await Fixture.Streams.AppendToStreamAsync( Assert.Equal(new(6), writeResult.NextExpectedStreamState); - var actual = await Fixture.Streams.ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + var actual = await Fixture.Streams.ReadStreamAsync(stream) .Select(x => x.Event) .ToArrayAsync(); - Assert.Equal(firstEvents.Concat(secondEvents).Select(x => x.EventId), actual.Select(x => x.EventId)); + Assert.Equal(firstEvents.Concat(secondEvents).Select(x => x.MessageId), actual.Select(x => x.EventId)); Assert.Equal( Enumerable.Range(2, 5).Select(i => new StreamPosition((ulong)i)), actual.Select(x => x.EventNumber) @@ -340,7 +347,7 @@ public async Task recreated_on_empty_when_metadata_set() { await Assert.ThrowsAsync( () => Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + .ReadStreamAsync(stream) .ToArrayAsync().AsTask() ); @@ -397,7 +404,7 @@ public async Task recreated_on_non_empty_when_metadata_set() { await Task.Delay(200); var actual = await Fixture.Streams - .ReadStreamAsync(Direction.Forwards, stream, StreamPosition.Start) + .ReadStreamAsync(stream) .ToArrayAsync(); Assert.Empty(actual); diff --git a/test/KurrentDB.Client.Tests/Streams/StreamMetadataTests.cs b/test/KurrentDB.Client.Tests/Streams/StreamMetadataTests.cs index dc8b9863a..1ffb4a7ec 100644 --- a/test/KurrentDB.Client.Tests/Streams/StreamMetadataTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/StreamMetadataTests.cs @@ -1,12 +1,12 @@ using System.Text.Json; -using KurrentDB.Client; using Grpc.Core; namespace KurrentDB.Client.Tests.Streams; [Trait("Category", "Target:Streams")] [Trait("Category", "Operation:Metadata")] -public class StreamMetadataTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) : KurrentPermanentTests(output, fixture) { +public class StreamMetadataTests(ITestOutputHelper output, KurrentDBPermanentFixture fixture) + : KurrentPermanentTests(output, fixture) { [Fact] public async Task getting_for_an_existing_stream_and_no_metadata_exists() { var stream = Fixture.GetStreamName(); @@ -102,7 +102,7 @@ await Fixture.Streams.SetStreamMetadataAsync( stream, StreamState.StreamRevision(2), new(), - options => { options.ThrowOnAppendFailure = false; } + new SetStreamMetadataOptions { ThrowOnAppendFailure = false } ); Assert.IsType(writeResult); @@ -162,7 +162,7 @@ public async Task with_timeout_set_with_any_stream_revision_fails_when_operation stream, StreamState.Any, new(), - deadline: TimeSpan.Zero + new SetStreamMetadataOptions { Deadline = TimeSpan.Zero } ) ); @@ -179,7 +179,7 @@ public async Task with_timeout_set_with_stream_revision_fails_when_operation_exp stream, StreamState.StreamRevision(0), new(), - deadline: TimeSpan.Zero + new SetStreamMetadataOptions { Deadline = TimeSpan.Zero } ) ); @@ -188,7 +188,10 @@ public async Task with_timeout_set_with_stream_revision_fails_when_operation_exp [Fact] public async Task with_timeout_get_fails_when_operation_expired() { - var stream = Fixture.GetStreamName(); - var rpcException = await Assert.ThrowsAsync(() => Fixture.Streams.GetStreamMetadataAsync(stream, TimeSpan.Zero)); + var stream = Fixture.GetStreamName(); + var rpcException = + await Assert.ThrowsAsync( + () => Fixture.Streams.GetStreamMetadataAsync(stream, new OperationOptions { Deadline = TimeSpan.Zero }) + ); } } diff --git a/test/KurrentDB.Client.Tests/Streams/SubscriptionFilter.cs b/test/KurrentDB.Client.Tests/Streams/SubscriptionFilter.cs index 44b592985..7ca9a1203 100644 --- a/test/KurrentDB.Client.Tests/Streams/SubscriptionFilter.cs +++ b/test/KurrentDB.Client.Tests/Streams/SubscriptionFilter.cs @@ -4,13 +4,13 @@ namespace KurrentDB.Client.Tests.Streams; -public record SubscriptionFilter(string Name, Func Create, Func PrepareEvent) { +public record SubscriptionFilter(string Name, Func Create, Func PrepareEvent) { public override string ToString() => Name; static readonly SubscriptionFilter StreamNamePrefix = new(nameof(StreamNamePrefix), StreamFilter.Prefix, (_, evt) => evt); static readonly SubscriptionFilter StreamNameRegex = new(nameof(StreamNameRegex), f => StreamFilter.RegularExpression(f), (_, evt) => evt); - static readonly SubscriptionFilter EventTypePrefix = new(nameof(EventTypePrefix), EventTypeFilter.Prefix, (term, evt) => new(evt.EventId, term, evt.Data, evt.Metadata, evt.ContentType)); - static readonly SubscriptionFilter EventTypeRegex = new(nameof(EventTypeRegex), f => EventTypeFilter.RegularExpression(f), (term, evt) => new(evt.EventId, term, evt.Data, evt.Metadata, evt.ContentType)); + static readonly SubscriptionFilter EventTypePrefix = new(nameof(EventTypePrefix), EventTypeFilter.Prefix, (term, evt) => new(term, evt.Data, evt.Metadata, evt.MessageId, evt.ContentType)); + static readonly SubscriptionFilter EventTypeRegex = new(nameof(EventTypeRegex), f => EventTypeFilter.RegularExpression(f), (term, evt) => new(term, evt.Data, evt.Metadata, evt.MessageId, evt.ContentType)); static SubscriptionFilter() { All = new[] { diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToAllObsoleteTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToAllObsoleteTests.cs index 38729cad6..f25f3ab75 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToAllObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToAllObsoleteTests.cs @@ -17,17 +17,17 @@ public async Task receives_all_events_from_start() { var seedEvents = Fixture.CreateTestEvents(10).ToArray(); var pageSize = seedEvents.Length / 2; - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); foreach (var evt in seedEvents.Take(pageSize)) - await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); using var subscription = await Fixture.Streams .SubscribeToAllAsync(FromAll.Start, OnReceived, false, OnDropped) .WithTimeout(); foreach (var evt in seedEvents.Skip(pageSize)) - await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); await receivedAllEvents.Task.WithTimeout(); @@ -64,7 +64,7 @@ public async Task receives_all_events_from_end() { var seedEvents = Fixture.CreateTestEvents(10).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); using var subscription = await Fixture.Streams .SubscribeToAllAsync(FromAll.End, OnReceived, false, OnDropped) @@ -72,7 +72,7 @@ public async Task receives_all_events_from_end() { // add the events we want to receive after we start the subscription foreach (var evt in seedEvents) - await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); await receivedAllEvents.Task.WithTimeout(); @@ -111,11 +111,11 @@ public async Task receives_all_events_from_position() { var pageSize = seedEvents.Length / 2; // only the second half of the events will be received - var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); IWriteResult writeResult = new SuccessResult(); foreach (var evt in seedEvents.Take(pageSize)) - writeResult = await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + writeResult = await Fixture.Streams.AppendToStreamAsync($"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); var position = FromAll.After(writeResult.LogPosition); @@ -124,7 +124,7 @@ public async Task receives_all_events_from_position() { .WithTimeout(); foreach (var evt in seedEvents.Skip(pageSize)) - await Fixture.Streams.AppendToStreamAsync($"stream-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + await Fixture.Streams.AppendToStreamAsync($"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); await receivedAllEvents.Task.WithTimeout(); @@ -162,7 +162,7 @@ public async Task receives_all_events_with_resolved_links() { var subscriptionDropped = new TaskCompletionSource(); var seedEvents = Fixture.CreateTestEvents(3).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); @@ -219,7 +219,7 @@ public async Task receives_all_filtered_events_from_start(SubscriptionFilter fil var pageSize = seedEvents.Length / 2; - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); // add noise await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents(3)); @@ -233,7 +233,7 @@ public async Task receives_all_filtered_events_from_start(SubscriptionFilter fil // add some of the events we want to see before we start the subscription foreach (var evt in seedEvents.Take(pageSize)) - await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1, CheckpointReached); @@ -243,7 +243,7 @@ public async Task receives_all_filtered_events_from_start(SubscriptionFilter fil // add some of the events we want to see after we start the subscription foreach (var evt in seedEvents.Skip(pageSize)) - await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); // wait until all events were received and at least one checkpoint was reached? await receivedAllEvents.Task.WithTimeout(); @@ -322,7 +322,7 @@ public async Task receives_all_filtered_events_from_end(SubscriptionFilter filte var pageSize = seedEvents.Length / 2; // only the second half of the events will be received - var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); // add noise await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents(3)); @@ -332,7 +332,7 @@ public async Task receives_all_filtered_events_from_end(SubscriptionFilter filte // add some of the events that are a match to the filter but will not be received foreach (var evt in seedEvents.Take(pageSize)) - await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1, CheckpointReached); @@ -342,7 +342,7 @@ public async Task receives_all_filtered_events_from_end(SubscriptionFilter filte // add the events we want to receive after we start the subscription foreach (var evt in seedEvents.Skip(pageSize)) - await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); // wait until all events were received and at least one checkpoint was reached? await receivedAllEvents.Task.WithTimeout(); @@ -421,7 +421,7 @@ public async Task receives_all_filtered_events_from_position(SubscriptionFilter var pageSize = seedEvents.Length / 2; // only the second half of the events will be received - var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); // add noise await Fixture.Streams.AppendToStreamAsync(Fixture.GetStreamName(), StreamState.NoStream, Fixture.CreateTestEvents(3)); @@ -432,7 +432,7 @@ public async Task receives_all_filtered_events_from_position(SubscriptionFilter // add some of the events that are a match to the filter but will not be received IWriteResult writeResult = new SuccessResult(); foreach (var evt in seedEvents.Take(pageSize)) - writeResult = await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + writeResult = await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); var position = FromAll.After(writeResult.LogPosition); @@ -444,7 +444,7 @@ public async Task receives_all_filtered_events_from_position(SubscriptionFilter // add the events we want to receive after we start the subscription foreach (var evt in seedEvents.Skip(pageSize)) - await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.EventId.ToGuid():N}", StreamState.NoStream, new[] { evt }); + await Fixture.Streams.AppendToStreamAsync($"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, new[] { evt }); // wait until all events were received and at least one checkpoint was reached? await receivedAllEvents.Task.WithTimeout(); @@ -513,7 +513,7 @@ public async Task receives_all_filtered_events_with_resolved_links() { var subscriptionDropped = new TaskCompletionSource(); var seedEvents = Fixture.CreateTestEvents(3).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToStreamObsoleteTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToStreamObsoleteTests.cs index acf15b47d..58ab9e411 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToStreamObsoleteTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/Obsolete/SubscribeToStreamObsoleteTests.cs @@ -18,7 +18,7 @@ public async Task receives_all_events_from_start() { var seedEvents = Fixture.CreateTestEvents(10).ToArray(); var pageSize = seedEvents.Length / 2; - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); @@ -67,7 +67,7 @@ public async Task receives_all_events_from_position() { var pageSize = seedEvents.Length / 2; // only the second half of the events will be received - var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); var writeResult = await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); var streamPosition = StreamPosition.FromInt64(writeResult.NextExpectedStreamState.ToInt64()); @@ -116,7 +116,7 @@ public async Task receives_all_events_from_non_existing_stream() { var seedEvents = Fixture.CreateTestEvents(10).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); using var subscription = await Fixture.Streams .SubscribeToStreamAsync(streamName, FromStream.Start, OnReceived, false, OnDropped) @@ -265,7 +265,7 @@ public async Task receives_all_events_with_resolved_links() { var subscriptionDropped = new TaskCompletionSource(); var seedEvents = Fixture.CreateTestEvents(3).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs index 35ea319bd..9662df450 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToAllTests.cs @@ -13,16 +13,16 @@ public async Task receives_all_events_from_start() { var seedEvents = Fixture.CreateTestEvents(10).ToArray(); var pageSize = seedEvents.Length / 2; - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); foreach (var evt in seedEvents.Take(pageSize)) await Fixture.Streams.AppendToStreamAsync( - $"stream-{evt.EventId.ToGuid():N}", + $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); - await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.Start); + await using var subscription = Fixture.Streams.SubscribeToAll(); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -31,9 +31,9 @@ await Fixture.Streams.AppendToStreamAsync( foreach (var evt in seedEvents.Skip(pageSize)) await Fixture.Streams.AppendToStreamAsync( - $"stream-{evt.EventId.ToGuid():N}", + $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); await Subscribe().WithTimeout(); @@ -59,9 +59,9 @@ async Task Subscribe() { public async Task receives_all_events_from_end() { var seedEvents = Fixture.CreateTestEvents(10).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); - await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.End); + await using var subscription = Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().FromEnd()); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -71,9 +71,9 @@ public async Task receives_all_events_from_end() { // add the events we want to receive after we start the subscription foreach (var evt in seedEvents) await Fixture.Streams.AppendToStreamAsync( - $"stream-{evt.EventId.ToGuid():N}", + $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); await Subscribe().WithTimeout(); @@ -101,19 +101,19 @@ public async Task receives_all_events_from_position() { var pageSize = seedEvents.Length / 2; // only the second half of the events will be received - var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); IWriteResult writeResult = new SuccessResult(); foreach (var evt in seedEvents.Take(pageSize)) writeResult = await Fixture.Streams.AppendToStreamAsync( - $"stream-{evt.EventId.ToGuid():N}", + $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); var position = FromAll.After(writeResult.LogPosition); - await using var subscription = Fixture.Streams.SubscribeToAll(position); + await using var subscription = Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().From(position)); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -122,9 +122,9 @@ public async Task receives_all_events_from_position() { foreach (var evt in seedEvents.Skip(pageSize)) await Fixture.Streams.AppendToStreamAsync( - $"stream-{evt.EventId.ToGuid():N}", + $"stream-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); await Subscribe().WithTimeout(); @@ -151,11 +151,11 @@ public async Task receives_all_events_with_resolved_links() { var streamName = Fixture.GetStreamName(); var seedEvents = Fixture.CreateTestEvents(3).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); - await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.Start, true); + await using var subscription = Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().WithResolveLinkTos()); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -194,7 +194,7 @@ public async Task receives_all_filtered_events_from_start(SubscriptionFilter fil var pageSize = seedEvents.Length / 2; - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); // add noise await Fixture.Streams.AppendToStreamAsync( @@ -203,7 +203,7 @@ await Fixture.Streams.AppendToStreamAsync( Fixture.CreateTestEvents(3) ); - var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start) + var existingEventsCount = await Fixture.Streams.ReadAllAsync() .Messages.CountAsync(); Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); @@ -215,14 +215,14 @@ await Fixture.Streams.AppendToStreamAsync( // add some of the events we want to see before we start the subscription foreach (var evt in seedEvents.Take(pageSize)) await Fixture.Streams.AppendToStreamAsync( - $"{streamPrefix}-{evt.EventId.ToGuid():N}", + $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1); - await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.Start, filterOptions: filterOptions); + await using var subscription = Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().WithFilter(filterOptions)); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -232,9 +232,9 @@ await Fixture.Streams.AppendToStreamAsync( // add some of the events we want to see after we start the subscription foreach (var evt in seedEvents.Skip(pageSize)) await Fixture.Streams.AppendToStreamAsync( - $"{streamPrefix}-{evt.EventId.ToGuid():N}", + $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); bool checkpointReached = false; @@ -281,7 +281,7 @@ public async Task receives_all_filtered_events_from_end(SubscriptionFilter filte var pageSize = seedEvents.Length / 2; // only the second half of the events will be received - var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); // add noise await Fixture.Streams.AppendToStreamAsync( @@ -290,7 +290,7 @@ await Fixture.Streams.AppendToStreamAsync( Fixture.CreateTestEvents(3) ); - var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start) + var existingEventsCount = await Fixture.Streams.ReadAllAsync() .Messages.CountAsync(); Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); @@ -302,14 +302,14 @@ await Fixture.Streams.AppendToStreamAsync( // add some of the events we want to see before we start the subscription foreach (var evt in seedEvents.Take(pageSize)) await Fixture.Streams.AppendToStreamAsync( - $"{streamPrefix}-{evt.EventId.ToGuid():N}", + $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1); - await using var subscription = Fixture.Streams.SubscribeToAll(FromAll.End, filterOptions: filterOptions); + await using var subscription = Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().FromEnd().WithFilter(filterOptions)); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -319,9 +319,9 @@ await Fixture.Streams.AppendToStreamAsync( // add some of the events we want to see after we start the subscription foreach (var evt in seedEvents.Skip(pageSize)) await Fixture.Streams.AppendToStreamAsync( - $"{streamPrefix}-{evt.EventId.ToGuid():N}", + $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); bool checkpointReached = false; @@ -368,7 +368,7 @@ public async Task receives_all_filtered_events_from_position(SubscriptionFilter var pageSize = seedEvents.Length / 2; // only the second half of the events will be received - var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); // add noise await Fixture.Streams.AppendToStreamAsync( @@ -377,7 +377,7 @@ await Fixture.Streams.AppendToStreamAsync( Fixture.CreateTestEvents(3) ); - var existingEventsCount = await Fixture.Streams.ReadAllAsync(Direction.Forwards, Position.Start) + var existingEventsCount = await Fixture.Streams.ReadAllAsync() .Messages.CountAsync(); Fixture.Log.Debug("Existing events count: {ExistingEventsCount}", existingEventsCount); @@ -386,16 +386,16 @@ await Fixture.Streams.AppendToStreamAsync( IWriteResult writeResult = new SuccessResult(); foreach (var evt in seedEvents.Take(pageSize)) writeResult = await Fixture.Streams.AppendToStreamAsync( - $"{streamPrefix}-{evt.EventId.ToGuid():N}", + $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); var position = FromAll.After(writeResult.LogPosition); var filterOptions = new SubscriptionFilterOptions(filter.Create(streamPrefix), 1); - await using var subscription = Fixture.Streams.SubscribeToAll(position, filterOptions: filterOptions); + await using var subscription = Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().From(position).WithFilter(filterOptions)); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -405,9 +405,9 @@ await Fixture.Streams.AppendToStreamAsync( // add the events we want to receive after we start the subscription foreach (var evt in seedEvents.Skip(pageSize)) await Fixture.Streams.AppendToStreamAsync( - $"{streamPrefix}-{evt.EventId.ToGuid():N}", + $"{streamPrefix}-{evt.MessageId.ToGuid():N}", StreamState.NoStream, - new[] { evt } + [evt] ); bool checkpointReached = false; @@ -445,14 +445,15 @@ public async Task receives_all_filtered_events_with_resolved_links() { var streamName = Fixture.GetStreamName(); var seedEvents = Fixture.CreateTestEvents(3).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); - var filterOptions = new SubscriptionFilterOptions(StreamFilter.Prefix($"$et-{KurrentDBPermanentFixture.TestEventType}")); + var filterOptions = + new SubscriptionFilterOptions(StreamFilter.Prefix($"$et-{KurrentDBPermanentFixture.TestEventType}")); await using var subscription = - Fixture.Streams.SubscribeToAll(FromAll.Start, true, filterOptions: filterOptions); + Fixture.Streams.SubscribeToAll(new SubscribeToAllOptions().WithResolveLinkTos().WithFilter(filterOptions)); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); @@ -467,7 +468,9 @@ public async Task receives_all_filtered_events_with_resolved_links() { async Task Subscribe() { while (await enumerator.MoveNextAsync()) { if (enumerator.Current is not StreamMessage.Event(var resolvedEvent) || - !resolvedEvent.OriginalEvent.EventStreamId.StartsWith($"$et-{KurrentDBPermanentFixture.TestEventType}")) { + !resolvedEvent.OriginalEvent.EventStreamId.StartsWith( + $"$et-{KurrentDBPermanentFixture.TestEventType}" + )) { continue; } @@ -487,10 +490,14 @@ await Streams.SetStreamMetadataAsync( SystemStreams.AllStream, StreamState.NoStream, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); - await Streams.AppendToStreamAsync($"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", StreamState.NoStream, CreateTestEvents(10)); + await Streams.AppendToStreamAsync( + $"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", + StreamState.NoStream, + CreateTestEvents(10) + ); }; } } diff --git a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs index bfeda3a60..7bcd8b733 100644 --- a/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs +++ b/test/KurrentDB.Client.Tests/Streams/Subscriptions/SubscribeToStreamTests.cs @@ -1,5 +1,3 @@ -using KurrentDB.Client; - namespace KurrentDB.Client.Tests.Streams; [Trait("Category", "Subscriptions")] @@ -13,11 +11,11 @@ public async Task receives_all_events_from_start() { var seedEvents = Fixture.CreateTestEvents(10).ToArray(); var pageSize = seedEvents.Length / 2; - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); - await using var subscription = Fixture.Streams.SubscribeToStream(streamName, FromStream.Start); + await using var subscription = Fixture.Streams.SubscribeToStream(streamName); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -53,7 +51,7 @@ public async Task receives_all_events_from_position() { var pageSize = seedEvents.Length / 2; // only the second half of the events will be received - var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Skip(pageSize).Select(x => x.MessageId)); var writeResult = await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents.Take(pageSize)); @@ -61,8 +59,12 @@ public async Task receives_all_events_from_position() { var streamPosition = StreamPosition.FromInt64(writeResult.NextExpectedStreamState.ToInt64()); var checkpoint = FromStream.After(streamPosition); - await using var subscription = Fixture.Streams.SubscribeToStream(streamName, checkpoint); - await using var enumerator = subscription.Messages.GetAsyncEnumerator(); + await using var subscription = Fixture.Streams.SubscribeToStream( + streamName, + new SubscribeToStreamOptions().From(checkpoint) + ); + + await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -99,9 +101,9 @@ public async Task receives_all_events_from_non_existing_stream() { var seedEvents = Fixture.CreateTestEvents(10).ToArray(); - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); - await using var subscription = Fixture.Streams.SubscribeToStream(streamName, FromStream.Start); + await using var subscription = Fixture.Streams.SubscribeToStream(streamName); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -137,14 +139,14 @@ public async Task allow_multiple_subscriptions_to_same_stream() { await Fixture.Streams.AppendToStreamAsync(streamName, StreamState.NoStream, seedEvents); - await using var subscription1 = Fixture.Streams.SubscribeToStream(streamName, FromStream.Start); + await using var subscription1 = Fixture.Streams.SubscribeToStream(streamName); await using var enumerator1 = subscription1.Messages.GetAsyncEnumerator(); Assert.True(await enumerator1.MoveNextAsync()); Assert.IsType(enumerator1.Current); - await using var subscription2 = Fixture.Streams.SubscribeToStream(streamName, FromStream.Start); + await using var subscription2 = Fixture.Streams.SubscribeToStream(streamName); await using var enumerator2 = subscription2.Messages.GetAsyncEnumerator(); Assert.True(await enumerator2.MoveNextAsync()); @@ -156,7 +158,7 @@ public async Task allow_multiple_subscriptions_to_same_stream() { return; async Task Subscribe(IAsyncEnumerator subscription) { - var availableEvents = new HashSet(seedEvents.Select(x => x.EventId)); + var availableEvents = new HashSet(seedEvents.Select(x => x.MessageId)); while (await subscription.MoveNextAsync()) { if (subscription.Current is not StreamMessage.Event(var resolvedEvent)) { @@ -176,7 +178,7 @@ async Task Subscribe(IAsyncEnumerator subscription) { public async Task drops_when_stream_tombstoned() { var streamName = Fixture.GetStreamName(); - await using var subscription = Fixture.Streams.SubscribeToStream(streamName, FromStream.Start); + await using var subscription = Fixture.Streams.SubscribeToStream(streamName); await using var enumerator = subscription.Messages.GetAsyncEnumerator(); Assert.True(await enumerator.MoveNextAsync()); @@ -202,10 +204,14 @@ await Streams.SetStreamMetadataAsync( SystemStreams.AllStream, StreamState.Any, new(acl: new(SystemRoles.All)), - userCredentials: TestCredentials.Root + new SetStreamMetadataOptions { UserCredentials = TestCredentials.Root } ); - await Streams.AppendToStreamAsync($"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", StreamState.NoStream, CreateTestEvents(10)); + await Streams.AppendToStreamAsync( + $"SubscriptionsFixture-Noise-{Guid.NewGuid():N}", + StreamState.NoStream, + CreateTestEvents(10) + ); }; } }