|
|
|
@ -5,7 +5,9 @@ |
|
|
|
// All rights reserved. Licensed under the MIT license.
|
|
|
|
// ==========================================================================
|
|
|
|
|
|
|
|
using System.Globalization; |
|
|
|
using FakeItEasy; |
|
|
|
using FluentAssertions; |
|
|
|
using Xunit; |
|
|
|
|
|
|
|
namespace Squidex.Infrastructure.EventSourcing |
|
|
|
@ -28,7 +30,7 @@ namespace Squidex.Infrastructure.EventSourcing |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task Should_propagate_exception_to_subscriber() |
|
|
|
public async Task Should_forward_exception_to_subscriber() |
|
|
|
{ |
|
|
|
var ex = new InvalidOperationException(); |
|
|
|
|
|
|
|
@ -44,7 +46,7 @@ namespace Squidex.Infrastructure.EventSourcing |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task Should_propagate_operation_cancelled_exception_to_subscriber() |
|
|
|
public async Task Should_forward_operation_cancelled_exception_to_subscriber() |
|
|
|
{ |
|
|
|
var ex = new OperationCanceledException(); |
|
|
|
|
|
|
|
@ -60,7 +62,7 @@ namespace Squidex.Infrastructure.EventSourcing |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task Should_propagate_aggregate_operation_cancelled_exception_to_subscriber() |
|
|
|
public async Task Should_forward_aggregate_operation_cancelled_exception_to_subscriber() |
|
|
|
{ |
|
|
|
var ex = new AggregateException(new OperationCanceledException()); |
|
|
|
|
|
|
|
@ -88,6 +90,69 @@ namespace Squidex.Infrastructure.EventSourcing |
|
|
|
.MustHaveHappened(2, Times.Exactly); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task Should_forward_events_to_subscriber() |
|
|
|
{ |
|
|
|
var events = Enumerable.Range(0, 50).Select(CreateEvent).ToArray(); |
|
|
|
|
|
|
|
var receivedEvents = new List<StoredEvent>(); |
|
|
|
|
|
|
|
A.CallTo(() => eventStore.QueryAllAsync("^my-stream", position, A<int>._, A<CancellationToken>._)) |
|
|
|
.Returns(events.ToAsyncEnumerable()); |
|
|
|
|
|
|
|
A.CallTo(() => eventSubscriber.OnNextAsync(A<IEventSubscription>._, A<StoredEvent>._)) |
|
|
|
.Invokes(x => receivedEvents.Add(x.GetArgument<StoredEvent>(1)!)); |
|
|
|
|
|
|
|
var sut = new PollingSubscription(eventStore, eventSubscriber, "^my-stream", position); |
|
|
|
|
|
|
|
sut.WakeUp(); |
|
|
|
|
|
|
|
await WaitAndStopAsync(sut); |
|
|
|
|
|
|
|
receivedEvents.Should().BeEquivalentTo(events); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task Should_receive_missing_events_with_second_pull() |
|
|
|
{ |
|
|
|
var events1 = Enumerable.Range(0, 200).Where(x => x % 2 == 0).Select(CreateEvent).ToArray(); |
|
|
|
var events2 = Enumerable.Range(0, 200).Where(x => x % 2 == 1).Select(CreateEvent).ToArray(); |
|
|
|
|
|
|
|
var receivedEvents = new List<StoredEvent>(); |
|
|
|
|
|
|
|
A.CallTo(() => eventStore.QueryAllAsync("^my-stream", position, A<int>._, A<CancellationToken>._)) |
|
|
|
.Returns(events1.ToAsyncEnumerable()); |
|
|
|
|
|
|
|
A.CallTo(() => eventStore.QueryAllAsync("^my-stream", "100", A<int>._, A<CancellationToken>._)) |
|
|
|
.Returns(events2.ToAsyncEnumerable()); |
|
|
|
|
|
|
|
A.CallTo(() => eventSubscriber.OnNextAsync(A<IEventSubscription>._, A<StoredEvent>._)) |
|
|
|
.Invokes(x => receivedEvents.Add(x.GetArgument<StoredEvent>(1)!)); |
|
|
|
|
|
|
|
var sut = new PollingSubscription(eventStore, eventSubscriber, "^my-stream", position); |
|
|
|
|
|
|
|
sut.WakeUp(); |
|
|
|
|
|
|
|
await WaitAndStopAsync(sut); |
|
|
|
|
|
|
|
receivedEvents.Should().BeEquivalentTo(events1.Union(events2)); |
|
|
|
} |
|
|
|
|
|
|
|
private StoredEvent CreateEvent(int position) |
|
|
|
{ |
|
|
|
return new StoredEvent( |
|
|
|
"my-stream", |
|
|
|
position.ToString(CultureInfo.InvariantCulture)!, |
|
|
|
position, |
|
|
|
new EventData( |
|
|
|
"type", |
|
|
|
new EnvelopeHeaders |
|
|
|
{ |
|
|
|
[CommonHeaders.EventId] = Guid.NewGuid().ToString() |
|
|
|
}, |
|
|
|
"payload")); |
|
|
|
} |
|
|
|
|
|
|
|
private static async Task WaitAndStopAsync(IEventSubscription sut) |
|
|
|
{ |
|
|
|
await Task.Delay(200); |
|
|
|
|