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)
{
if (take <= 0)
@ -118,7 +118,7 @@ namespace Squidex.Infrastructure.EventSourcing
var find =
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;
@ -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)
{
StreamPosition lastPosition = position;
@ -158,7 +158,7 @@ namespace Squidex.Infrastructure.EventSourcing
var find =
Collection.Find(filterDefinition)
.Limit((int)take).Sort(Sort.Ascending(TimestampField).Ascending(EventStreamField));
.Limit(take).Sort(Sort.Ascending(TimestampField).Ascending(EventStreamField));
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);
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);

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

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

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

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

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

@ -26,7 +26,7 @@ namespace Squidex.Infrastructure.EventSourcing
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();
}
@ -35,7 +35,7 @@ namespace Squidex.Infrastructure.EventSourcing
{
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);
var sut = new PollingSubscription(eventStore, eventSubscriber, "^my-stream", position);
@ -51,7 +51,7 @@ namespace Squidex.Infrastructure.EventSourcing
{
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);
var sut = new PollingSubscription(eventStore, eventSubscriber, "^my-stream", position);
@ -67,7 +67,7 @@ namespace Squidex.Infrastructure.EventSourcing
{
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);
var sut = new PollingSubscription(eventStore, eventSubscriber, "^my-stream", position);
@ -87,7 +87,7 @@ namespace Squidex.Infrastructure.EventSourcing
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);
}

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

@ -16,38 +16,46 @@ describe('Duration', () => {
});
it('should calculate timestamp from first and second time', () => {
const s = DateTime.today();
const d = s.addSeconds(100);
const time1 = DateTime.today();
const time2 = time1.addSeconds(100);
const duration = Duration.create(s, d);
const duration = Duration.create(time1, time2);
const actual = duration.timestamp;
const expected = 100000;
expect(actual).toBe(expected);
expect(actual).toBe(100000);
});
it('should print to string correctly', () => {
const s = DateTime.today();
const d = s.addHours(12).addMinutes(30).addSeconds(60);
const time1 = DateTime.today();
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 expected = '12:31:00';
expect(actual).toBe(expected);
expect(actual).toBe('12:31:00');
});
it('should print to string correctly for one digit minutes', () => {
const s = DateTime.today();
const d = s.addHours(1).addMinutes(2).addSeconds(5);
const time1 = DateTime.today();
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 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;
let secondsString = seconds.toString();
let secondsString = Math.ceil(seconds).toString();
if (secondsString.length === 1) {
secondsString = `0${secondsString}`;

Loading…
Cancel
Save