|
|
|
@ -19,7 +19,7 @@ using Xunit; |
|
|
|
|
|
|
|
namespace Squidex.Infrastructure.CQRS.Events |
|
|
|
{ |
|
|
|
public class EventReceiverTests : IDisposable |
|
|
|
public class EventReceiverTests |
|
|
|
{ |
|
|
|
public sealed class MyEvent : IEvent |
|
|
|
{ |
|
|
|
@ -36,18 +36,29 @@ namespace Squidex.Infrastructure.CQRS.Events |
|
|
|
public string Error { get; set; } |
|
|
|
} |
|
|
|
|
|
|
|
private sealed class MyLog : ISemanticLog |
|
|
|
private sealed class MyEventStore : IEventStore |
|
|
|
{ |
|
|
|
public Dictionary<SemanticLogLevel, int> LogCount { get; } = new Dictionary<SemanticLogLevel, int>(); |
|
|
|
private readonly IEnumerable<StoredEvent> storedEvents; |
|
|
|
|
|
|
|
public void Log(SemanticLogLevel logLevel, Action<IObjectWriter> action) |
|
|
|
public MyEventStore(IEnumerable<StoredEvent> storedEvents) |
|
|
|
{ |
|
|
|
var count = LogCount.GetOrDefault(logLevel); |
|
|
|
this.storedEvents = storedEvents; |
|
|
|
} |
|
|
|
|
|
|
|
LogCount[logLevel] = count + 1; |
|
|
|
public async Task GetEventsAsync(Func<StoredEvent, Task> callback, CancellationToken cancellationToken, string streamName = null, long lastReceivedEventNumber = -1) |
|
|
|
{ |
|
|
|
foreach (var @event in storedEvents) |
|
|
|
{ |
|
|
|
await callback(@event); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public ISemanticLog CreateScope(Action<IObjectWriter> objectWriter) |
|
|
|
public IObservable<StoredEvent> GetEventsAsync(string streamName = null, long lastReceivedEventNumber = -1) |
|
|
|
{ |
|
|
|
throw new NotSupportedException(); |
|
|
|
} |
|
|
|
|
|
|
|
public Task AppendEventsAsync(Guid commitId, string streamName, int expectedVersion, IEnumerable<EventData> events) |
|
|
|
{ |
|
|
|
throw new NotSupportedException(); |
|
|
|
} |
|
|
|
@ -56,7 +67,7 @@ namespace Squidex.Infrastructure.CQRS.Events |
|
|
|
private readonly Mock<IEventConsumerInfoRepository> eventConsumerInfoRepository = new Mock<IEventConsumerInfoRepository>(); |
|
|
|
private readonly Mock<IEventConsumer> eventConsumer = new Mock<IEventConsumer>(); |
|
|
|
private readonly Mock<IEventNotifier> eventNotifier = new Mock<IEventNotifier>(); |
|
|
|
private readonly Mock<IEventStore> eventStore = new Mock<IEventStore>(); |
|
|
|
private readonly Mock<ISemanticLog> log = new Mock<ISemanticLog>(); |
|
|
|
private readonly Mock<EventDataFormatter> formatter = new Mock<EventDataFormatter>(new TypeNameRegistry(), null); |
|
|
|
private readonly EventData eventData1 = new EventData(); |
|
|
|
private readonly EventData eventData2 = new EventData(); |
|
|
|
@ -65,23 +76,21 @@ namespace Squidex.Infrastructure.CQRS.Events |
|
|
|
private readonly Envelope<IEvent> envelope2 = new Envelope<IEvent>(new MyEvent()); |
|
|
|
private readonly Envelope<IEvent> envelope3 = new Envelope<IEvent>(new MyEvent()); |
|
|
|
private readonly EventReceiver sut; |
|
|
|
private readonly MyLog log = new MyLog(); |
|
|
|
private readonly StoredEvent[] events; |
|
|
|
private readonly MyEventConsumerInfo consumerInfo = new MyEventConsumerInfo(); |
|
|
|
private readonly string consumerName; |
|
|
|
|
|
|
|
public EventReceiverTests() |
|
|
|
{ |
|
|
|
events = new[] |
|
|
|
var events = new[] |
|
|
|
{ |
|
|
|
new StoredEvent(3, 3, eventData1), |
|
|
|
new StoredEvent(4, 4, eventData2), |
|
|
|
new StoredEvent(4, 4, eventData3) |
|
|
|
new StoredEvent(5, 5, eventData3) |
|
|
|
}; |
|
|
|
|
|
|
|
consumerName = eventConsumer.Object.GetType().Name; |
|
|
|
|
|
|
|
ExceptEvents(2); |
|
|
|
var eventStore = new MyEventStore(events); |
|
|
|
|
|
|
|
eventConsumer.Setup(x => x.Name).Returns(consumerName); |
|
|
|
eventConsumerInfoRepository.Setup(x => x.FindAsync(consumerName)).Returns(Task.FromResult<IEventConsumerInfo>(consumerInfo)); |
|
|
|
@ -90,12 +99,7 @@ namespace Squidex.Infrastructure.CQRS.Events |
|
|
|
formatter.Setup(x => x.Parse(eventData2)).Returns(envelope2); |
|
|
|
formatter.Setup(x => x.Parse(eventData3)).Returns(envelope3); |
|
|
|
|
|
|
|
sut = new EventReceiver(formatter.Object, eventStore.Object, eventNotifier.Object, eventConsumerInfoRepository.Object, log); |
|
|
|
} |
|
|
|
|
|
|
|
public void Dispose() |
|
|
|
{ |
|
|
|
sut.Dispose(); |
|
|
|
sut = new EventReceiver(formatter.Object, eventStore, eventNotifier.Object, eventConsumerInfoRepository.Object, log.Object); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
@ -103,21 +107,20 @@ namespace Squidex.Infrastructure.CQRS.Events |
|
|
|
{ |
|
|
|
sut.Subscribe(eventConsumer.Object); |
|
|
|
sut.Subscribe(eventConsumer.Object); |
|
|
|
sut.Next(); |
|
|
|
sut.Dispose(); |
|
|
|
|
|
|
|
eventConsumerInfoRepository.Verify(x => x.CreateAsync(consumerName), Times.Once()); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task Should_subscribe_to_consumer_and_handle_events() |
|
|
|
public void Should_subscribe_to_consumer_and_handle_events() |
|
|
|
{ |
|
|
|
consumerInfo.LastHandledEventNumber = 2L; |
|
|
|
|
|
|
|
sut.Subscribe(eventConsumer.Object); |
|
|
|
|
|
|
|
await Task.Delay(20); |
|
|
|
|
|
|
|
Assert.Equal(1, log.LogCount.Count); |
|
|
|
Assert.Equal(6, log.LogCount[SemanticLogLevel.Information]); |
|
|
|
sut.Next(); |
|
|
|
sut.Dispose(); |
|
|
|
|
|
|
|
eventConsumer.Verify(x => x.On(envelope1), Times.Once()); |
|
|
|
eventConsumer.Verify(x => x.On(envelope2), Times.Once()); |
|
|
|
@ -125,7 +128,7 @@ namespace Squidex.Infrastructure.CQRS.Events |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task Should_abort_if_handling_failed() |
|
|
|
public void Should_abort_if_handling_failed() |
|
|
|
{ |
|
|
|
consumerInfo.LastHandledEventNumber = 2L; |
|
|
|
|
|
|
|
@ -133,13 +136,8 @@ namespace Squidex.Infrastructure.CQRS.Events |
|
|
|
eventConsumer.Setup(x => x.On(envelope2)).Throws(new InvalidOperationException()); |
|
|
|
|
|
|
|
sut.Subscribe(eventConsumer.Object); |
|
|
|
|
|
|
|
await Task.Delay(20); |
|
|
|
|
|
|
|
Assert.Equal(3, log.LogCount.Count); |
|
|
|
Assert.Equal(1, log.LogCount[SemanticLogLevel.Error]); |
|
|
|
Assert.Equal(1, log.LogCount[SemanticLogLevel.Fatal]); |
|
|
|
Assert.Equal(3, log.LogCount[SemanticLogLevel.Information]); |
|
|
|
sut.Next(); |
|
|
|
sut.Dispose(); |
|
|
|
|
|
|
|
eventConsumer.Verify(x => x.On(envelope1), Times.Once()); |
|
|
|
eventConsumer.Verify(x => x.On(envelope2), Times.Once()); |
|
|
|
@ -149,19 +147,15 @@ namespace Squidex.Infrastructure.CQRS.Events |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task Should_abort_if_serialization_failed() |
|
|
|
public void Should_abort_if_serialization_failed() |
|
|
|
{ |
|
|
|
consumerInfo.LastHandledEventNumber = 2L; |
|
|
|
|
|
|
|
formatter.Setup(x => x.Parse(eventData2)).Throws(new InvalidOperationException()); |
|
|
|
|
|
|
|
sut.Subscribe(eventConsumer.Object); |
|
|
|
|
|
|
|
await Task.Delay(20); |
|
|
|
|
|
|
|
Assert.Equal(2, log.LogCount.Count); |
|
|
|
Assert.Equal(2, log.LogCount[SemanticLogLevel.Fatal]); |
|
|
|
Assert.Equal(2, log.LogCount[SemanticLogLevel.Information]); |
|
|
|
sut.Next(); |
|
|
|
sut.Dispose(); |
|
|
|
|
|
|
|
eventConsumer.Verify(x => x.On(envelope1), Times.Once()); |
|
|
|
eventConsumer.Verify(x => x.On(envelope2), Times.Never()); |
|
|
|
@ -171,19 +165,14 @@ namespace Squidex.Infrastructure.CQRS.Events |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task Should_reset_if_requested() |
|
|
|
public void Should_reset_if_requested() |
|
|
|
{ |
|
|
|
consumerInfo.IsResetting = true; |
|
|
|
consumerInfo.LastHandledEventNumber = 2L; |
|
|
|
|
|
|
|
ExceptEvents(-1); |
|
|
|
|
|
|
|
sut.Subscribe(eventConsumer.Object); |
|
|
|
|
|
|
|
await Task.Delay(20); |
|
|
|
|
|
|
|
Assert.Equal(1, log.LogCount.Count); |
|
|
|
Assert.Equal(8, log.LogCount[SemanticLogLevel.Information]); |
|
|
|
|
|
|
|
sut.Subscribe(eventConsumer.Object, autoTrigger: false); |
|
|
|
sut.Next(); |
|
|
|
sut.Dispose(); |
|
|
|
|
|
|
|
eventConsumer.Verify(x => x.On(envelope1), Times.Once()); |
|
|
|
eventConsumer.Verify(x => x.On(envelope2), Times.Once()); |
|
|
|
@ -191,20 +180,5 @@ namespace Squidex.Infrastructure.CQRS.Events |
|
|
|
|
|
|
|
eventConsumer.Verify(x => x.ClearAsync(), Times.Once()); |
|
|
|
} |
|
|
|
|
|
|
|
private void ExceptEvents(int eventNumber) |
|
|
|
{ |
|
|
|
eventStore.Setup(x => x.GetEventsAsync(It.IsAny<Func<StoredEvent, Task>>(), CancellationToken.None, null, eventNumber)) |
|
|
|
.Callback<Func<StoredEvent, Task>, CancellationToken, string, long>(ReturnEvents) |
|
|
|
.Returns(TaskHelper.Done); |
|
|
|
} |
|
|
|
|
|
|
|
private void ReturnEvents(Func<StoredEvent, Task> callback, CancellationToken cancellationToken, string streamName, long lastReceivedEventNumber) |
|
|
|
{ |
|
|
|
foreach (var storedEvent in events) |
|
|
|
{ |
|
|
|
callback(storedEvent).Wait(cancellationToken); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|