Browse Source

Improved tests for event stores and asset stores.

pull/349/head
Sebastian Stehle 7 years ago
parent
commit
97002fe130
  1. 5
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs
  2. 1
      src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs
  3. 66
      src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs
  4. 7
      src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Writer.cs
  5. 2
      src/Squidex.Infrastructure/EventSourcing/IEventStore.cs
  6. 19
      tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs
  7. 20
      tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreFixture.cs
  8. 14
      tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs
  9. 35
      tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreFixture.cs
  10. 17
      tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs
  11. 25
      tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreFixture.cs
  12. 14
      tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs
  13. 4
      tests/Squidex.Infrastructure.Tests/Assets/MemoryAssetStoreTests.cs
  14. 40
      tests/Squidex.Infrastructure.Tests/Assets/MongoGridFSAssetStoreFixture.cs
  15. 25
      tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs
  16. 265
      tests/Squidex.Infrastructure.Tests/EventSourcing/EventStoreTests.cs
  17. 42
      tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreFixture.cs
  18. 29
      tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreTests.cs
  19. 11
      tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs

5
src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs

@ -189,11 +189,6 @@ namespace Squidex.Infrastructure.EventSourcing
}
}
public Task DeleteManyAsync(string property, object value)
{
throw new NotSupportedException();
}
private string GetStreamName(string streamName)
{
return $"{prefix}-{streamName}";

1
src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs

@ -15,6 +15,7 @@ namespace Squidex.Infrastructure.EventSourcing
{
public partial class MongoEventStore : MongoRepositoryBase<MongoEventCommit>, IEventStore
{
private const int MaxCommitSize = 10;
private static readonly FieldDefinition<MongoEventCommit, BsonTimestamp> TimestampField = Fields.Build(x => x.Timestamp);
private static readonly FieldDefinition<MongoEventCommit, long> EventsCountField = Fields.Build(x => x.EventsCount);
private static readonly FieldDefinition<MongoEventCommit, long> EventStreamOffsetField = Fields.Build(x => x.EventStreamOffset);

66
src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs

@ -10,11 +10,15 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.MongoDb;
using EventFilter = MongoDB.Driver.FilterDefinition<Squidex.Infrastructure.EventSourcing.MongoEventCommit>;
namespace Squidex.Infrastructure.EventSourcing
{
public delegate bool EventPredicate(EventData data);
public partial class MongoEventStore : MongoRepositoryBase<MongoEventCommit>, IEventStore
{
public Task CreateIndexAsync(string property)
@ -39,7 +43,7 @@ namespace Squidex.Infrastructure.EventSourcing
await Collection.Find(
Filter.And(
Filter.Eq(EventStreamField, streamName),
Filter.Gte(EventStreamOffsetField, streamPosition - 1)))
Filter.Gte(EventStreamOffsetField, streamPosition - MaxCommitSize)))
.Sort(Sort.Ascending(TimestampField)).ToListAsync();
var result = new List<StoredEvent>();
@ -75,9 +79,10 @@ namespace Squidex.Infrastructure.EventSourcing
StreamPosition lastPosition = position;
var filter = CreateFilter(property, value, lastPosition);
var filterDefinition = CreateFilter(property, value, lastPosition);
var filterExpression = CreateFilterExpression(property, value);
return QueryAsync(callback, lastPosition, filter, ct);
return QueryAsync(callback, lastPosition, filterDefinition, filterExpression, ct);
}
public Task QueryAsync(Func<StoredEvent, Task> callback, string streamFilter = null, string position = null, CancellationToken ct = default)
@ -86,16 +91,17 @@ namespace Squidex.Infrastructure.EventSourcing
StreamPosition lastPosition = position;
var filter = CreateFilter(streamFilter, lastPosition);
var filterDefinition = CreateFilter(streamFilter, lastPosition);
var filterExpression = CreateFilterExpression(null, null);
return QueryAsync(callback, lastPosition, filter, ct);
return QueryAsync(callback, lastPosition, filterDefinition, filterExpression, ct);
}
private async Task QueryAsync(Func<StoredEvent, Task> callback, StreamPosition lastPosition, FilterDefinition<MongoEventCommit> filter, CancellationToken ct = default)
private async Task QueryAsync(Func<StoredEvent, Task> callback, StreamPosition lastPosition, EventFilter filterDefinition, EventPredicate filterExpression, CancellationToken ct = default)
{
using (Profiler.TraceMethod<MongoEventStore>())
{
await Collection.Find(filter).Sort(Sort.Ascending(TimestampField)).ForEachPipelineAsync(async commit =>
await Collection.Find(filterDefinition).Sort(Sort.Ascending(TimestampField)).ForEachPipelineAsync(async commit =>
{
var eventStreamOffset = (int)commit.EventStreamOffset;
@ -109,43 +115,47 @@ namespace Squidex.Infrastructure.EventSourcing
if (commitOffset > lastPosition.CommitOffset || commitTimestamp > lastPosition.Timestamp)
{
var eventData = e.ToEventData();
var eventToken = new StreamPosition(commitTimestamp, commitOffset, commit.Events.Length);
await callback(new StoredEvent(commit.EventStream, eventToken, eventStreamOffset, eventData));
if (filterExpression(eventData))
{
var eventToken = new StreamPosition(commitTimestamp, commitOffset, commit.Events.Length);
commitOffset++;
await callback(new StoredEvent(commit.EventStream, eventToken, eventStreamOffset, eventData));
}
}
commitOffset++;
}
}, ct);
}
}
private static FilterDefinition<MongoEventCommit> CreateFilter(string property, object value, StreamPosition streamPosition)
private static EventFilter CreateFilter(string property, object value, StreamPosition streamPosition)
{
var filters = new List<FilterDefinition<MongoEventCommit>>();
var filters = new List<EventFilter>();
FilterByPosition(streamPosition, filters);
FilterByProperty(property, value, filters);
AppendByPosition(streamPosition, filters);
AppendByProperty(property, value, filters);
return Filter.And(filters);
}
private static FilterDefinition<MongoEventCommit> CreateFilter(string streamFilter, StreamPosition streamPosition)
private static EventFilter CreateFilter(string streamFilter, StreamPosition streamPosition)
{
var filters = new List<FilterDefinition<MongoEventCommit>>();
var filters = new List<EventFilter>();
FilterByPosition(streamPosition, filters);
FilterByStream(streamFilter, filters);
AppendByPosition(streamPosition, filters);
AppendByStream(streamFilter, filters);
return Filter.And(filters);
}
private static void FilterByProperty(string property, object value, List<FilterDefinition<MongoEventCommit>> filters)
private static void AppendByProperty(string property, object value, List<EventFilter> filters)
{
filters.Add(Filter.Eq(CreateIndexPath(property), value));
}
private static void FilterByStream(string streamFilter, List<FilterDefinition<MongoEventCommit>> filters)
private static void AppendByStream(string streamFilter, List<EventFilter> filters)
{
if (!string.IsNullOrWhiteSpace(streamFilter) && !string.Equals(streamFilter, ".*", StringComparison.OrdinalIgnoreCase))
{
@ -160,7 +170,7 @@ namespace Squidex.Infrastructure.EventSourcing
}
}
private static void FilterByPosition(StreamPosition streamPosition, List<FilterDefinition<MongoEventCommit>> filters)
private static void AppendByPosition(StreamPosition streamPosition, List<EventFilter> filters)
{
if (streamPosition.IsEndOfCommit)
{
@ -172,6 +182,20 @@ namespace Squidex.Infrastructure.EventSourcing
}
}
private static EventPredicate CreateFilterExpression(string property, object value)
{
if (!string.IsNullOrWhiteSpace(property))
{
var jsonValue = JsonValue.Create(value);
return x => x.Headers.TryGetValue(property, out var p) && p.Equals(jsonValue);
}
else
{
return x => true;
}
}
private static string CreateIndexPath(string property)
{
return $"Events.Metadata.{property}";

7
src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Writer.cs

@ -24,11 +24,6 @@ namespace Squidex.Infrastructure.EventSourcing
return Collection.DeleteManyAsync(x => x.EventStream == streamName);
}
public Task DeleteManyAsync(string property, object value)
{
return Collection.DeleteManyAsync(Filter.Eq(CreateIndexPath(property), value));
}
public Task AppendAsync(Guid commitId, string streamName, ICollection<EventData> events)
{
return AppendAsync(commitId, streamName, EtagVersion.Any, events);
@ -36,6 +31,8 @@ namespace Squidex.Infrastructure.EventSourcing
public async Task AppendAsync(Guid commitId, string streamName, long expectedVersion, ICollection<EventData> events)
{
Guard.LessThan(events.Count, MaxCommitSize, "events.Count");
using (Profiler.TraceMethod<MongoEventStore>())
{
Guard.GreaterEquals(expectedVersion, EtagVersion.Any, nameof(expectedVersion));

2
src/Squidex.Infrastructure/EventSourcing/IEventStore.cs

@ -28,8 +28,6 @@ namespace Squidex.Infrastructure.EventSourcing
Task DeleteStreamAsync(string streamName);
Task DeleteManyAsync(string property, object value);
IEventSubscription CreateSubscription(IEventSubscriber subscriber, string streamFilter, string position = null);
}
}

19
tests/Squidex.Infrastructure.Tests/Assets/AssetStoreTests.cs

@ -12,23 +12,13 @@ using Xunit;
namespace Squidex.Infrastructure.Assets
{
public abstract class AssetStoreTests<T> : IDisposable where T : IAssetStore
public abstract class AssetStoreTests<T> where T : IAssetStore
{
private readonly MemoryStream assetData = new MemoryStream(new byte[] { 0x1, 0x2, 0x3, 0x4 });
private readonly string assetId = Guid.NewGuid().ToString();
private readonly string tempId = Guid.NewGuid().ToString();
private readonly Lazy<T> sut;
protected AssetStoreTests()
{
sut = new Lazy<T>(CreateStore);
if (Sut is IInitializable initializable)
{
initializable.InitializeAsync().Wait();
}
}
protected T Sut
{
get { return sut.Value; }
@ -39,9 +29,12 @@ namespace Squidex.Infrastructure.Assets
get { return assetId; }
}
public abstract T CreateStore();
protected AssetStoreTests()
{
sut = new Lazy<T>(CreateStore);
}
public abstract void Dispose();
public abstract T CreateStore();
[Fact]
public virtual Task Should_throw_exception_if_asset_to_download_is_not_found()

20
tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreFixture.cs

@ -0,0 +1,20 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Assets
{
public sealed class AzureBlobAssetStoreFixture
{
public AzureBlobAssetStore AssetStore { get; }
public AzureBlobAssetStoreFixture()
{
AssetStore = new AzureBlobAssetStore("UseDevelopmentStorage=true", "squidex-test-container");
AssetStore.InitializeAsync().Wait();
}
}
}

14
tests/Squidex.Infrastructure.Tests/Assets/AzureBlobAssetStoreTests.cs

@ -7,19 +7,21 @@
using Xunit;
#pragma warning disable xUnit1000 // Test classes must be public
namespace Squidex.Infrastructure.Assets
{
internal class AzureBlobAssetStoreTests : AssetStoreTests<AzureBlobAssetStore>
[Trait("Dependency", "Azure")]
public class AzureBlobAssetStoreTests : AssetStoreTests<AzureBlobAssetStore>, IClassFixture<AzureBlobAssetStoreFixture>
{
public override AzureBlobAssetStore CreateStore()
private readonly AzureBlobAssetStoreFixture fixture;
public AzureBlobAssetStoreTests(AzureBlobAssetStoreFixture fixture)
{
return new AzureBlobAssetStore("UseDevelopmentStorage=true", "squidex-test-container");
this.fixture = fixture;
}
public override void Dispose()
public override AzureBlobAssetStore CreateStore()
{
return fixture.AssetStore;
}
[Fact]

35
tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreFixture.cs

@ -0,0 +1,35 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.IO;
using FakeItEasy;
using Squidex.Infrastructure.Log;
namespace Squidex.Infrastructure.Assets
{
public sealed class FolderAssetStoreFixture : IDisposable
{
public string TestFolder { get; } = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
public FolderAssetStore AssetStore { get; }
public FolderAssetStoreFixture()
{
AssetStore = new FolderAssetStore(TestFolder, A.Dummy<ISemanticLog>());
AssetStore.InitializeAsync().Wait();
}
public void Dispose()
{
if (Directory.Exists(TestFolder))
{
Directory.Delete(TestFolder, true);
}
}
}
}

17
tests/Squidex.Infrastructure.Tests/Assets/FolderAssetStoreTests.cs

@ -13,21 +13,18 @@ using Xunit;
namespace Squidex.Infrastructure.Assets
{
public class FolderAssetStoreTests : AssetStoreTests<FolderAssetStore>
public class FolderAssetStoreTests : AssetStoreTests<FolderAssetStore>, IClassFixture<FolderAssetStoreFixture>
{
private readonly string testFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
private readonly FolderAssetStoreFixture fixture;
public override FolderAssetStore CreateStore()
public FolderAssetStoreTests(FolderAssetStoreFixture fixture)
{
return new FolderAssetStore(testFolder, A.Dummy<ISemanticLog>());
this.fixture = fixture;
}
public override void Dispose()
public override FolderAssetStore CreateStore()
{
if (Directory.Exists(testFolder))
{
Directory.Delete(testFolder, true);
}
return fixture.AssetStore;
}
[Fact]
@ -39,7 +36,7 @@ namespace Squidex.Infrastructure.Assets
[Fact]
public void Should_create_directory_when_connecting()
{
Assert.True(Directory.Exists(testFolder));
Assert.True(Directory.Exists(fixture.TestFolder));
}
[Fact]

25
tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreFixture.cs

@ -0,0 +1,25 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Infrastructure.Assets
{
public sealed class GoogleCloudAssetStoreFixture : IDisposable
{
public GoogleCloudAssetStore AssetStore { get; }
public GoogleCloudAssetStoreFixture()
{
AssetStore = new GoogleCloudAssetStore("squidex-test");
}
public void Dispose()
{
}
}
}

14
tests/Squidex.Infrastructure.Tests/Assets/GoogleCloudAssetStoreTests.cs

@ -7,19 +7,21 @@
using Xunit;
#pragma warning disable xUnit1000 // Test classes must be public
namespace Squidex.Infrastructure.Assets
{
internal class GoogleCloudAssetStoreTests : AssetStoreTests<GoogleCloudAssetStore>
[Trait("Dependency", "GC")]
public class GoogleCloudAssetStoreTests : AssetStoreTests<GoogleCloudAssetStore>, IClassFixture<GoogleCloudAssetStoreFixture>
{
public override GoogleCloudAssetStore CreateStore()
private readonly GoogleCloudAssetStoreFixture fixture;
public GoogleCloudAssetStoreTests(GoogleCloudAssetStoreFixture fixture)
{
return new GoogleCloudAssetStore("squidex-test");
this.fixture = fixture;
}
public override void Dispose()
public override GoogleCloudAssetStore CreateStore()
{
return fixture.AssetStore;
}
[Fact]

4
tests/Squidex.Infrastructure.Tests/Assets/MemoryAssetStoreTests.cs

@ -16,10 +16,6 @@ namespace Squidex.Infrastructure.Assets
return new MemoryAssetStore();
}
public override void Dispose()
{
}
[Fact]
public void Should_not_calculate_source_url()
{

40
tests/Squidex.Infrastructure.Tests/Assets/MongoGridFSAssetStoreFixture.cs

@ -0,0 +1,40 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using MongoDB.Driver;
using MongoDB.Driver.GridFS;
namespace Squidex.Infrastructure.Assets
{
public sealed class MongoGridFSAssetStoreFixture : IDisposable
{
public MongoGridFsAssetStore AssetStore { get; }
public IMongoClient MongoClient { get; } = new MongoClient("mongodb://localhost");
public IMongoDatabase MongoDatabase { get; }
public MongoGridFSAssetStoreFixture()
{
MongoDatabase = MongoClient.GetDatabase("GridFSTest");
var gridFSBucket = new GridFSBucket<string>(MongoDatabase, new GridFSBucketOptions
{
BucketName = "fs"
});
AssetStore = new MongoGridFsAssetStore(gridFSBucket);
AssetStore.InitializeAsync().Wait();
}
public void Dispose()
{
MongoClient.DropDatabase("GridFSTest");
}
}
}

25
tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs

@ -5,36 +5,23 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using MongoDB.Driver;
using MongoDB.Driver.GridFS;
using Xunit;
#pragma warning disable xUnit1000 // Test classes must be public
namespace Squidex.Infrastructure.Assets
{
internal class MongoGridFsAssetStoreTests : AssetStoreTests<MongoGridFsAssetStore>
[Trait("Dependency", "MongoDB")]
public class MongoGridFsAssetStoreTests : AssetStoreTests<MongoGridFsAssetStore>, IClassFixture<MongoGridFSAssetStoreFixture>
{
private static readonly IGridFSBucket<string> GridFSBucket;
private readonly MongoGridFSAssetStoreFixture fixture;
static MongoGridFsAssetStoreTests()
public MongoGridFsAssetStoreTests(MongoGridFSAssetStoreFixture fixture)
{
var mongoClient = new MongoClient("mongodb://localhost");
var mongoDatabase = mongoClient.GetDatabase("Test");
GridFSBucket = new GridFSBucket<string>(mongoDatabase, new GridFSBucketOptions
{
BucketName = "fs"
});
this.fixture = fixture;
}
public override MongoGridFsAssetStore CreateStore()
{
return new MongoGridFsAssetStore(GridFSBucket);
}
public override void Dispose()
{
return fixture.AssetStore;
}
[Fact]

265
tests/Squidex.Infrastructure.Tests/EventSourcing/EventStoreTests.cs

@ -0,0 +1,265 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Infrastructure.Tasks;
using Xunit;
namespace Squidex.Infrastructure.EventSourcing
{
public abstract class EventStoreTests<T> where T : IEventStore
{
private readonly Lazy<T> sut;
public sealed class EventSubscriber : IEventSubscriber
{
public List<StoredEvent> Events { get; } = new List<StoredEvent>();
public Task OnErrorAsync(IEventSubscription subscription, Exception exception)
{
throw new NotSupportedException();
}
public Task OnEventAsync(IEventSubscription subscription, StoredEvent storedEvent)
{
Events.Add(storedEvent);
return TaskHelper.Done;
}
}
protected T Sut
{
get { return sut.Value; }
}
protected abstract int SubscriptionDelayInMs { get; }
protected EventStoreTests()
{
sut = new Lazy<T>(CreateStore);
}
public abstract T CreateStore();
[Fact]
public async Task Should_throw_exception_for_version_mismatch()
{
var streamName = $"test-{Guid.NewGuid()}";
var events = new EventData[]
{
new EventData("Type1", new EnvelopeHeaders(), "1"),
new EventData("Type2", new EnvelopeHeaders(), "2"),
};
await Assert.ThrowsAsync<WrongEventVersionException>(() => Sut.AppendAsync(Guid.NewGuid(), streamName, 0, events));
}
[Fact]
public async Task Should_throw_exception_for_version_mismatch_and_update()
{
var streamName = $"test-{Guid.NewGuid()}";
var events = new EventData[]
{
new EventData("Type1", new EnvelopeHeaders(), "1"),
new EventData("Type2", new EnvelopeHeaders(), "2"),
};
await Sut.AppendAsync(Guid.NewGuid(), streamName, events);
await Assert.ThrowsAsync<WrongEventVersionException>(() => Sut.AppendAsync(Guid.NewGuid(), streamName, 0, events));
}
[Fact]
public async Task Should_append_events()
{
var streamName = $"test-{Guid.NewGuid()}";
var events = new EventData[]
{
new EventData("Type1", new EnvelopeHeaders(), "1"),
new EventData("Type2", new EnvelopeHeaders(), "2"),
};
await Sut.AppendAsync(Guid.NewGuid(), streamName, events);
var readEvents1 = await QueryAsync(streamName);
var readEvents2 = await QueryWithCallbackAsync(streamName);
var expected = new StoredEvent[]
{
new StoredEvent(streamName, "Position", 0, events[0]),
new StoredEvent(streamName, "Position", 1, events[1])
};
ShouldBeEquivalentTo(readEvents1, expected);
ShouldBeEquivalentTo(readEvents2, expected);
}
[Fact]
public async Task Should_subscribe_to_events()
{
var streamName = $"test-{Guid.NewGuid()}";
var events = new EventData[]
{
new EventData("Type1", new EnvelopeHeaders(), "1"),
new EventData("Type2", new EnvelopeHeaders(), "2"),
};
var subscriber = new EventSubscriber();
IEventSubscription subscription = null;
try
{
subscription = Sut.CreateSubscription(subscriber, streamName);
await Sut.AppendAsync(Guid.NewGuid(), streamName, events);
subscription.WakeUp();
await Task.Delay(SubscriptionDelayInMs);
var expected = new StoredEvent[]
{
new StoredEvent(streamName, "Position", 0, events[0]),
new StoredEvent(streamName, "Position", 1, events[1])
};
ShouldBeEquivalentTo(subscriber.Events, expected);
}
finally
{
await subscription.StopAsync();
}
}
[Fact]
public async Task Should_read_events_from_offset()
{
var streamName = $"test-{Guid.NewGuid()}";
var events = new EventData[]
{
new EventData("Type1", new EnvelopeHeaders(), "1"),
new EventData("Type2", new EnvelopeHeaders(), "2"),
};
await Sut.AppendAsync(Guid.NewGuid(), streamName, events);
var firstRead = await QueryAsync(streamName);
var readEvents1 = await QueryAsync(streamName, 1);
var readEvents2 = await QueryWithCallbackAsync(streamName, firstRead[0].EventPosition);
var expected = new StoredEvent[]
{
new StoredEvent(streamName, "Position", 1, events[1])
};
ShouldBeEquivalentTo(readEvents1, expected);
ShouldBeEquivalentTo(readEvents2, expected);
}
[Fact]
public async Task Should_delete_stream()
{
var streamName = $"test-{Guid.NewGuid()}";
var events = new EventData[]
{
new EventData("Type1", new EnvelopeHeaders(), "1"),
new EventData("Type2", new EnvelopeHeaders(), "2"),
};
await Sut.AppendAsync(Guid.NewGuid(), streamName, events);
await Sut.DeleteStreamAsync(streamName);
var readEvents1 = await QueryAsync(streamName);
var readEvents2 = await QueryWithCallbackAsync(streamName);
Assert.Empty(readEvents1);
Assert.Empty(readEvents2);
}
[Fact]
public async Task Should_query_events_by_property()
{
var keyed1 = new EnvelopeHeaders();
var keyed2 = new EnvelopeHeaders();
keyed1.Add("key", "1");
keyed2.Add("key", "2");
var streamName1 = $"test-{Guid.NewGuid()}";
var streamName2 = $"test-{Guid.NewGuid()}";
var events1 = new EventData[]
{
new EventData("Type1", keyed1, "1"),
new EventData("Type2", keyed2, "2"),
};
var events2 = new EventData[]
{
new EventData("Type3", keyed2, "3"),
new EventData("Type4", keyed1, "4"),
};
await Sut.CreateIndexAsync("key");
await Sut.AppendAsync(Guid.NewGuid(), streamName1, events1);
await Sut.AppendAsync(Guid.NewGuid(), streamName2, events2);
var readEvents = await QueryWithFilterAsync("key", "2");
var expected = new StoredEvent[]
{
new StoredEvent(streamName1, "Position", 1, events1[1]),
new StoredEvent(streamName2, "Position", 0, events2[0])
};
ShouldBeEquivalentTo(readEvents, expected);
}
private Task<IReadOnlyList<StoredEvent>> QueryAsync(string streamName, long position = EtagVersion.Any)
{
return Sut.QueryAsync(streamName, position);
}
private async Task<IReadOnlyList<StoredEvent>> QueryWithFilterAsync(string property, object value)
{
var readEvents = new List<StoredEvent>();
await Sut.QueryAsync(x => { readEvents.Add(x); return TaskHelper.Done; }, property, value);
return readEvents;
}
private async Task<IReadOnlyList<StoredEvent>> QueryWithCallbackAsync(string streamFilter = null, string position = null)
{
var readEvents = new List<StoredEvent>();
await Sut.QueryAsync(x => { readEvents.Add(x); return TaskHelper.Done; }, streamFilter, position);
return readEvents;
}
private void ShouldBeEquivalentTo(IEnumerable<StoredEvent> actual, params StoredEvent[] expected)
{
var actualArray = actual.Select(x => new StoredEvent(x.StreamName, "Position", x.EventStreamNumber, x.Data)).ToArray();
actualArray.Should().BeEquivalentTo(expected);
}
}
}

42
tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreFixture.cs

@ -0,0 +1,42 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using FakeItEasy;
using MongoDB.Driver;
using Newtonsoft.Json;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.TestHelpers;
namespace Squidex.Infrastructure.EventSourcing
{
public sealed class MongoEventStoreFixture : IDisposable
{
public MongoEventStore EventStore { get; }
public IMongoClient MongoClient { get; } = new MongoClient("mongodb://localhost");
public IMongoDatabase MongoDatabase { get; }
public IEventNotifier Notifier { get; } = A.Fake<IEventNotifier>();
public MongoEventStoreFixture()
{
MongoDatabase = MongoClient.GetDatabase("EventStoreTest");
BsonJsonConvention.Register(JsonSerializer.Create(JsonHelper.DefaultSettings()));
EventStore = new MongoEventStore(MongoDatabase, Notifier);
EventStore.InitializeAsync().Wait();
}
public void Dispose()
{
MongoClient.DropDatabase("EventStoreTest");
}
}
}

29
tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreTests.cs

@ -0,0 +1,29 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Xunit;
namespace Squidex.Infrastructure.EventSourcing
{
[Trait("Dependency", "MongoDB")]
public class MongoEventStoreTests : EventStoreTests<MongoEventStore>, IClassFixture<MongoEventStoreFixture>
{
private readonly MongoEventStoreFixture fixture;
protected override int SubscriptionDelayInMs { get; } = 1000;
public MongoEventStoreTests(MongoEventStoreFixture fixture)
{
this.fixture = fixture;
}
public override MongoEventStore CreateStore()
{
return fixture.EventStore;
}
}
}

11
tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs

@ -19,7 +19,14 @@ namespace Squidex.Infrastructure.TestHelpers
public static IJsonSerializer CreateSerializer(TypeNameRegistry typeNameRegistry = null)
{
var serializerSettings = new JsonSerializerSettings
var serializerSettings = DefaultSettings(typeNameRegistry);
return new NewtonsoftJsonSerializer(serializerSettings);
}
public static JsonSerializerSettings DefaultSettings(TypeNameRegistry typeNameRegistry = null)
{
return new JsonSerializerSettings
{
SerializationBinder = new TypeNameSerializationBinder(typeNameRegistry ?? new TypeNameRegistry()),
@ -37,8 +44,6 @@ namespace Squidex.Infrastructure.TestHelpers
TypeNameHandling = TypeNameHandling.Auto
};
return new NewtonsoftJsonSerializer(serializerSettings);
}
public static T SerializeAndDeserialize<T>(this T value)

Loading…
Cancel
Save