Browse Source

Fix/event store (#708)

* Fix take.

* Better tests.

* Fix build.

* Another fix.

* Reverted fix.
pull/711/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
57ae6ed0e5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs
  2. 4
      backend/src/Squidex.Infrastructure/EventSourcing/IEventStore.cs
  3. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RepairFilesTests.cs
  4. 2
      backend/tests/Squidex.Infrastructure.Tests/Collections/ImmutableListTests.cs
  5. 85
      backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EventStoreTests.cs
  6. 10
      backend/tests/Squidex.Infrastructure.Tests/EventSourcing/PollingSubscriptionTests.cs
  7. 38
      frontend/app/framework/utils/duration.spec.ts
  8. 2
      frontend/app/framework/utils/duration.ts

8
backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs

@ -104,7 +104,7 @@ namespace Squidex.Infrastructure.EventSourcing
} }
} }
public async IAsyncEnumerable<StoredEvent> QueryAllReverseAsync(string? streamFilter = null, Instant timestamp = default, long take = long.MaxValue, public async IAsyncEnumerable<StoredEvent> QueryAllReverseAsync(string? streamFilter = null, Instant timestamp = default, int take = int.MaxValue,
[EnumeratorCancellation] CancellationToken ct = default) [EnumeratorCancellation] CancellationToken ct = default)
{ {
if (take <= 0) if (take <= 0)
@ -118,7 +118,7 @@ namespace Squidex.Infrastructure.EventSourcing
var find = var find =
Collection.Find(filterDefinition, options: Batching.Options) Collection.Find(filterDefinition, options: Batching.Options)
.Limit((int)take).Sort(Sort.Descending(TimestampField).Ascending(EventStreamField)); .Limit(take).Sort(Sort.Descending(TimestampField).Ascending(EventStreamField));
var taken = 0; var taken = 0;
@ -149,7 +149,7 @@ namespace Squidex.Infrastructure.EventSourcing
} }
} }
public async IAsyncEnumerable<StoredEvent> QueryAllAsync(string? streamFilter = null, string? position = null, long take = long.MaxValue, public async IAsyncEnumerable<StoredEvent> QueryAllAsync(string? streamFilter = null, string? position = null, int take = int.MaxValue,
[EnumeratorCancellation] CancellationToken ct = default) [EnumeratorCancellation] CancellationToken ct = default)
{ {
StreamPosition lastPosition = position; StreamPosition lastPosition = position;
@ -158,7 +158,7 @@ namespace Squidex.Infrastructure.EventSourcing
var find = var find =
Collection.Find(filterDefinition) Collection.Find(filterDefinition)
.Limit((int)take).Sort(Sort.Ascending(TimestampField).Ascending(EventStreamField)); .Limit(take).Sort(Sort.Ascending(TimestampField).Ascending(EventStreamField));
var taken = 0; var taken = 0;

4
backend/src/Squidex.Infrastructure/EventSourcing/IEventStore.cs

@ -19,9 +19,9 @@ namespace Squidex.Infrastructure.EventSourcing
Task<IReadOnlyList<StoredEvent>> QueryAsync(string streamName, long streamPosition = 0); Task<IReadOnlyList<StoredEvent>> QueryAsync(string streamName, long streamPosition = 0);
IAsyncEnumerable<StoredEvent> QueryAllReverseAsync(string? streamFilter = null, Instant timestamp = default, long take = long.MaxValue, CancellationToken ct = default); IAsyncEnumerable<StoredEvent> QueryAllReverseAsync(string? streamFilter = null, Instant timestamp = default, int take = int.MaxValue, CancellationToken ct = default);
IAsyncEnumerable<StoredEvent> QueryAllAsync(string? streamFilter = null, string? position = null, long take = long.MaxValue, CancellationToken ct = default); IAsyncEnumerable<StoredEvent> QueryAllAsync(string? streamFilter = null, string? position = null, int take = int.MaxValue, CancellationToken ct = default);
Task AppendAsync(Guid commitId, string streamName, ICollection<EventData> events); Task AppendAsync(Guid commitId, string streamName, ICollection<EventData> events);

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RepairFilesTests.cs

@ -128,7 +128,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
.Returns(null); .Returns(null);
} }
A.CallTo(() => eventStore.QueryAllAsync("^asset\\-", null, long.MaxValue, default)) A.CallTo(() => eventStore.QueryAllAsync("^asset\\-", null, int.MaxValue, default))
.Returns(storedEvents.ToAsyncEnumerable()); .Returns(storedEvents.ToAsyncEnumerable());
} }
} }

2
backend/tests/Squidex.Infrastructure.Tests/Collections/ImmutableListTests.cs

@ -5,8 +5,8 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure.TestHelpers;
using System.Linq; using System.Linq;
using Squidex.Infrastructure.TestHelpers;
using Xunit; using Xunit;
namespace Squidex.Infrastructure.Collections namespace Squidex.Infrastructure.Collections

85
backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EventStoreTests.cs

@ -60,13 +60,13 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
var streamName = $"test-{Guid.NewGuid()}"; var streamName = $"test-{Guid.NewGuid()}";
var events = new[] var commit = new[]
{ {
CreateEventData(1), CreateEventData(1),
CreateEventData(2) CreateEventData(2)
}; };
await Assert.ThrowsAsync<WrongEventVersionException>(() => Sut.AppendAsync(Guid.NewGuid(), streamName, 0, events)); await Assert.ThrowsAsync<WrongEventVersionException>(() => Sut.AppendAsync(Guid.NewGuid(), streamName, 0, commit));
} }
[Fact] [Fact]
@ -74,15 +74,15 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
var streamName = $"test-{Guid.NewGuid()}"; var streamName = $"test-{Guid.NewGuid()}";
var events = new[] var commit = new[]
{ {
CreateEventData(1), CreateEventData(1),
CreateEventData(2) CreateEventData(2)
}; };
await Sut.AppendAsync(Guid.NewGuid(), streamName, events); await Sut.AppendAsync(Guid.NewGuid(), streamName, commit);
await Assert.ThrowsAsync<WrongEventVersionException>(() => Sut.AppendAsync(Guid.NewGuid(), streamName, 0, events)); await Assert.ThrowsAsync<WrongEventVersionException>(() => Sut.AppendAsync(Guid.NewGuid(), streamName, 0, commit));
} }
[Fact] [Fact]
@ -90,21 +90,30 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
var streamName = $"test-{Guid.NewGuid()}"; var streamName = $"test-{Guid.NewGuid()}";
var events = new[] var commit1 = new[]
{ {
CreateEventData(1), CreateEventData(1),
CreateEventData(2) CreateEventData(2)
}; };
await Sut.AppendAsync(Guid.NewGuid(), streamName, events); var commit2 = new[]
{
CreateEventData(1),
CreateEventData(2)
};
await Sut.AppendAsync(Guid.NewGuid(), streamName, commit1);
await Sut.AppendAsync(Guid.NewGuid(), streamName, commit2);
var readEvents1 = await QueryAsync(streamName); var readEvents1 = await QueryAsync(streamName);
var readEvents2 = await QueryAllAsync(streamName); var readEvents2 = await QueryAllAsync(streamName);
var expected = new[] var expected = new[]
{ {
new StoredEvent(streamName, "Position", 0, events[0]), new StoredEvent(streamName, "Position", 0, commit1[0]),
new StoredEvent(streamName, "Position", 1, events[1]) new StoredEvent(streamName, "Position", 1, commit1[1]),
new StoredEvent(streamName, "Position", 2, commit2[2]),
new StoredEvent(streamName, "Position", 3, commit2[3])
}; };
ShouldBeEquivalentTo(readEvents1, expected); ShouldBeEquivalentTo(readEvents1, expected);
@ -116,7 +125,7 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
var streamName = $"test-{Guid.NewGuid()}"; var streamName = $"test-{Guid.NewGuid()}";
var events = new[] var commit1 = new[]
{ {
CreateEventData(1), CreateEventData(1),
CreateEventData(2) CreateEventData(2)
@ -124,7 +133,7 @@ namespace Squidex.Infrastructure.EventSourcing
await Sut.AppendUnsafeAsync(new List<EventCommit> await Sut.AppendUnsafeAsync(new List<EventCommit>
{ {
new EventCommit(Guid.NewGuid(), streamName, -1, events) new EventCommit(Guid.NewGuid(), streamName, -1, commit1)
}); });
var readEvents1 = await QueryAsync(streamName); var readEvents1 = await QueryAsync(streamName);
@ -132,8 +141,8 @@ namespace Squidex.Infrastructure.EventSourcing
var expected = new[] var expected = new[]
{ {
new StoredEvent(streamName, "Position", 0, events[0]), new StoredEvent(streamName, "Position", 0, commit1[0]),
new StoredEvent(streamName, "Position", 1, events[1]) new StoredEvent(streamName, "Position", 1, commit1[1])
}; };
ShouldBeEquivalentTo(readEvents1, expected); ShouldBeEquivalentTo(readEvents1, expected);
@ -145,7 +154,7 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
var streamName = $"test-{Guid.NewGuid()}"; var streamName = $"test-{Guid.NewGuid()}";
var events = new[] var commit1 = new[]
{ {
CreateEventData(1), CreateEventData(1),
CreateEventData(2) CreateEventData(2)
@ -153,13 +162,13 @@ namespace Squidex.Infrastructure.EventSourcing
var readEvents = await QueryWithSubscriptionAsync(streamName, async () => var readEvents = await QueryWithSubscriptionAsync(streamName, async () =>
{ {
await Sut.AppendAsync(Guid.NewGuid(), streamName, events); await Sut.AppendAsync(Guid.NewGuid(), streamName, commit1);
}); });
var expected = new[] var expected = new[]
{ {
new StoredEvent(streamName, "Position", 0, events[0]), new StoredEvent(streamName, "Position", 0, commit1[0]),
new StoredEvent(streamName, "Position", 1, events[1]) new StoredEvent(streamName, "Position", 1, commit1[1])
}; };
ShouldBeEquivalentTo(readEvents, expected); ShouldBeEquivalentTo(readEvents, expected);
@ -170,7 +179,7 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
var streamName = $"test-{Guid.NewGuid()}"; var streamName = $"test-{Guid.NewGuid()}";
var events1 = new[] var commit1 = new[]
{ {
CreateEventData(1), CreateEventData(1),
CreateEventData(2) CreateEventData(2)
@ -179,10 +188,10 @@ namespace Squidex.Infrastructure.EventSourcing
// Append and read in parallel. // Append and read in parallel.
await QueryWithSubscriptionAsync(streamName, async () => await QueryWithSubscriptionAsync(streamName, async () =>
{ {
await Sut.AppendAsync(Guid.NewGuid(), streamName, events1); await Sut.AppendAsync(Guid.NewGuid(), streamName, commit1);
}); });
var events2 = new[] var commit2 = new[]
{ {
CreateEventData(1), CreateEventData(1),
CreateEventData(2) CreateEventData(2)
@ -191,23 +200,23 @@ namespace Squidex.Infrastructure.EventSourcing
// Append and read in parallel. // Append and read in parallel.
var readEventsFromPosition = await QueryWithSubscriptionAsync(streamName, async () => var readEventsFromPosition = await QueryWithSubscriptionAsync(streamName, async () =>
{ {
await Sut.AppendAsync(Guid.NewGuid(), streamName, events2); await Sut.AppendAsync(Guid.NewGuid(), streamName, commit2);
}); });
var expectedFromPosition = new[] var expectedFromPosition = new[]
{ {
new StoredEvent(streamName, "Position", 2, events2[0]), new StoredEvent(streamName, "Position", 2, commit2[0]),
new StoredEvent(streamName, "Position", 3, events2[1]) new StoredEvent(streamName, "Position", 3, commit2[1])
}; };
var readEventsFromBeginning = await QueryWithSubscriptionAsync(streamName, fromBeginning: true); var readEventsFromBeginning = await QueryWithSubscriptionAsync(streamName, fromBeginning: true);
var expectedFromBeginning = new[] var expectedFromBeginning = new[]
{ {
new StoredEvent(streamName, "Position", 0, events1[0]), new StoredEvent(streamName, "Position", 0, commit1[0]),
new StoredEvent(streamName, "Position", 1, events1[1]), new StoredEvent(streamName, "Position", 1, commit1[1]),
new StoredEvent(streamName, "Position", 2, events2[0]), new StoredEvent(streamName, "Position", 2, commit2[0]),
new StoredEvent(streamName, "Position", 3, events2[1]) new StoredEvent(streamName, "Position", 3, commit2[1])
}; };
ShouldBeEquivalentTo(readEventsFromPosition?.TakeLast(2), expectedFromPosition); ShouldBeEquivalentTo(readEventsFromPosition?.TakeLast(2), expectedFromPosition);
@ -219,13 +228,13 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
var streamName = $"test-{Guid.NewGuid()}"; var streamName = $"test-{Guid.NewGuid()}";
var events = new[] var commit = new[]
{ {
CreateEventData(1), CreateEventData(1),
CreateEventData(2) CreateEventData(2)
}; };
await Sut.AppendAsync(Guid.NewGuid(), streamName, events); await Sut.AppendAsync(Guid.NewGuid(), streamName, commit);
var firstRead = await QueryAsync(streamName); var firstRead = await QueryAsync(streamName);
@ -234,7 +243,7 @@ namespace Squidex.Infrastructure.EventSourcing
var expected = new[] var expected = new[]
{ {
new StoredEvent(streamName, "Position", 1, events[1]) new StoredEvent(streamName, "Position", 1, commit[1])
}; };
ShouldBeEquivalentTo(readEvents1, expected); ShouldBeEquivalentTo(readEvents1, expected);
@ -247,33 +256,33 @@ namespace Squidex.Infrastructure.EventSourcing
var streamName1 = $"test-{Guid.NewGuid()}"; var streamName1 = $"test-{Guid.NewGuid()}";
var streamName2 = $"test-{Guid.NewGuid()}"; var streamName2 = $"test-{Guid.NewGuid()}";
var events1 = new[] var stream1Commit = new[]
{ {
CreateEventData(1), CreateEventData(1),
CreateEventData(2) CreateEventData(2)
}; };
var events2 = new[] var stream2Commit = new[]
{ {
CreateEventData(3), CreateEventData(3),
CreateEventData(4) CreateEventData(4)
}; };
await Sut.AppendAsync(Guid.NewGuid(), streamName1, events1); await Sut.AppendAsync(Guid.NewGuid(), streamName1, stream1Commit);
await Sut.AppendAsync(Guid.NewGuid(), streamName2, events2); await Sut.AppendAsync(Guid.NewGuid(), streamName2, stream2Commit);
var readEvents = await Sut.QueryManyAsync(new[] { streamName1, streamName2 }); var readEvents = await Sut.QueryManyAsync(new[] { streamName1, streamName2 });
var expected1 = new[] var expected1 = new[]
{ {
new StoredEvent(streamName1, "Position", 0, events1[0]), new StoredEvent(streamName1, "Position", 0, stream1Commit[0]),
new StoredEvent(streamName1, "Position", 1, events1[1]) new StoredEvent(streamName1, "Position", 1, stream1Commit[1])
}; };
var expected2 = new[] var expected2 = new[]
{ {
new StoredEvent(streamName2, "Position", 0, events2[0]), new StoredEvent(streamName2, "Position", 0, stream2Commit[0]),
new StoredEvent(streamName2, "Position", 1, events2[1]) new StoredEvent(streamName2, "Position", 1, stream2Commit[1])
}; };
ShouldBeEquivalentTo(readEvents[streamName1], expected1); ShouldBeEquivalentTo(readEvents[streamName1], expected1);

10
backend/tests/Squidex.Infrastructure.Tests/EventSourcing/PollingSubscriptionTests.cs

@ -26,7 +26,7 @@ namespace Squidex.Infrastructure.EventSourcing
await WaitAndStopAsync(sut); await WaitAndStopAsync(sut);
A.CallTo(() => eventStore.QueryAllAsync("^my-stream", position, A<long>._, A<CancellationToken>._)) A.CallTo(() => eventStore.QueryAllAsync("^my-stream", position, A<int>._, A<CancellationToken>._))
.MustHaveHappenedOnceExactly(); .MustHaveHappenedOnceExactly();
} }
@ -35,7 +35,7 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
var ex = new InvalidOperationException(); var ex = new InvalidOperationException();
A.CallTo(() => eventStore.QueryAllAsync("^my-stream", position, A<long>._, A<CancellationToken>._)) A.CallTo(() => eventStore.QueryAllAsync("^my-stream", position, A<int>._, A<CancellationToken>._))
.Throws(ex); .Throws(ex);
var sut = new PollingSubscription(eventStore, eventSubscriber, "^my-stream", position); var sut = new PollingSubscription(eventStore, eventSubscriber, "^my-stream", position);
@ -51,7 +51,7 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
var ex = new OperationCanceledException(); var ex = new OperationCanceledException();
A.CallTo(() => eventStore.QueryAllAsync("^my-stream", position, A<long>._, A<CancellationToken>._)) A.CallTo(() => eventStore.QueryAllAsync("^my-stream", position, A<int>._, A<CancellationToken>._))
.Throws(ex); .Throws(ex);
var sut = new PollingSubscription(eventStore, eventSubscriber, "^my-stream", position); var sut = new PollingSubscription(eventStore, eventSubscriber, "^my-stream", position);
@ -67,7 +67,7 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
var ex = new AggregateException(new OperationCanceledException()); var ex = new AggregateException(new OperationCanceledException());
A.CallTo(() => eventStore.QueryAllAsync("^my-stream", position, A<long>._, A<CancellationToken>._)) A.CallTo(() => eventStore.QueryAllAsync("^my-stream", position, A<int>._, A<CancellationToken>._))
.Throws(ex); .Throws(ex);
var sut = new PollingSubscription(eventStore, eventSubscriber, "^my-stream", position); var sut = new PollingSubscription(eventStore, eventSubscriber, "^my-stream", position);
@ -87,7 +87,7 @@ namespace Squidex.Infrastructure.EventSourcing
await WaitAndStopAsync(sut); await WaitAndStopAsync(sut);
A.CallTo(() => eventStore.QueryAllAsync("^my-stream", position, A<long>._, A<CancellationToken>._)) A.CallTo(() => eventStore.QueryAllAsync("^my-stream", position, A<int>._, A<CancellationToken>._))
.MustHaveHappened(2, Times.Exactly); .MustHaveHappened(2, Times.Exactly);
} }

38
frontend/app/framework/utils/duration.spec.ts

@ -16,38 +16,46 @@ describe('Duration', () => {
}); });
it('should calculate timestamp from first and second time', () => { it('should calculate timestamp from first and second time', () => {
const s = DateTime.today(); const time1 = DateTime.today();
const d = s.addSeconds(100); const time2 = time1.addSeconds(100);
const duration = Duration.create(s, d); const duration = Duration.create(time1, time2);
const actual = duration.timestamp; const actual = duration.timestamp;
const expected = 100000;
expect(actual).toBe(expected); expect(actual).toBe(100000);
}); });
it('should print to string correctly', () => { it('should print to string correctly', () => {
const s = DateTime.today(); const time1 = DateTime.today();
const d = s.addHours(12).addMinutes(30).addSeconds(60); const time2 = time1.addHours(12).addMinutes(30).addSeconds(60);
const duration = Duration.create(s, d); const duration = Duration.create(time1, time2);
const actual = duration.toString(); const actual = duration.toString();
const expected = '12:31:00';
expect(actual).toBe(expected); expect(actual).toBe('12:31:00');
}); });
it('should print to string correctly for one digit minutes', () => { it('should print to string correctly for one digit minutes', () => {
const s = DateTime.today(); const time1 = DateTime.today();
const d = s.addHours(1).addMinutes(2).addSeconds(5); const time2 = time1.addHours(1).addMinutes(2).addSeconds(5);
const duration = Duration.create(s, d); const duration = Duration.create(time1, time2);
const actual = duration.toString(); const actual = duration.toString();
const expected = '01:02:05';
expect(actual).toBe(expected); expect(actual).toBe('01:02:05');
});
it('should print to string correctly for one partial seconds', () => {
const time1 = DateTime.today();
const time2 = time1.addHours(1).addMinutes(2).addSeconds(4.555334);
const duration = Duration.create(time1, time2);
const actual = duration.toString();
expect(actual).toBe('01:02:04');
}); });
}); });

2
frontend/app/framework/utils/duration.ts

@ -43,7 +43,7 @@ export class Duration {
seconds %= 60; seconds %= 60;
let secondsString = seconds.toString(); let secondsString = Math.ceil(seconds).toString();
if (secondsString.length === 1) { if (secondsString.length === 1) {
secondsString = `0${secondsString}`; secondsString = `0${secondsString}`;

Loading…
Cancel
Save