diff --git a/src/Squidex.Infrastructure/Commands/AggregateHandler.cs b/src/Squidex.Infrastructure/Commands/AggregateHandler.cs index 530f22591..b30298026 100644 --- a/src/Squidex.Infrastructure/Commands/AggregateHandler.cs +++ b/src/Squidex.Infrastructure/Commands/AggregateHandler.cs @@ -99,6 +99,8 @@ namespace Squidex.Infrastructure.Commands { var domainObject = await stateFactory.GetSingleAsync(domainObjectId.ToString()); + await handler(domainObject); + await domainObject.WriteAsync(log); if (!context.IsCompleted) diff --git a/src/Squidex.Infrastructure/Language.cs b/src/Squidex.Infrastructure/Language.cs index aedd0f070..f392a2f5c 100644 --- a/src/Squidex.Infrastructure/Language.cs +++ b/src/Squidex.Infrastructure/Language.cs @@ -14,10 +14,10 @@ namespace Squidex.Infrastructure { public sealed partial class Language { - private static readonly Regex CultureRegex = new Regex("^([a-z]{2})(\\-[a-z]{2})?$"); + private static readonly Regex CultureRegex = new Regex("^([a-z]{2})(\\-[a-z]{2})?$", RegexOptions.IgnoreCase); + private static readonly Dictionary AllLanguagesField = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly string iso2Code; private readonly string englishName; - private static readonly Dictionary AllLanguagesField = new Dictionary(StringComparer.OrdinalIgnoreCase); private static Language AddLanguage(string iso2Code, string englishName) { @@ -106,7 +106,7 @@ namespace Squidex.Infrastructure return null; } - input = match.Groups[0].Value; + input = match.Groups[1].Value; } if (TryGetLanguage(input.ToLowerInvariant(), out var result)) diff --git a/tests/Squidex.Infrastructure.Tests/Commands/AggregateHandlerTests.cs b/tests/Squidex.Infrastructure.Tests/Commands/AggregateHandlerTests.cs index 3285fed20..1a020997d 100644 --- a/tests/Squidex.Infrastructure.Tests/Commands/AggregateHandlerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Commands/AggregateHandlerTests.cs @@ -47,6 +47,9 @@ namespace Squidex.Infrastructure.Commands A.CallTo(() => stateFactory.CreateAsync(domainObjectId.ToString())) .Returns(Task.FromResult(domainObject)); + A.CallTo(() => stateFactory.GetSingleAsync(domainObjectId.ToString())) + .Returns(Task.FromResult(domainObject)); + sut = new AggregateHandler(stateFactory, serviceProvider, log); domainObject.ActivateAsync(domainObjectId.ToString(), store).Wait(); @@ -58,6 +61,12 @@ namespace Squidex.Infrastructure.Commands return Assert.ThrowsAnyAsync(() => sut.CreateAsync(new CommandContext(A.Dummy()), x => TaskHelper.False)); } + [Fact] + public Task Create_synced_with_task_should_throw_exception_if_not_aggregate_command() + { + return Assert.ThrowsAnyAsync(() => sut.CreateSyncedAsync(new CommandContext(A.Dummy()), x => TaskHelper.False)); + } + [Fact] public async Task Create_with_task_should_create_domain_object_and_save() { @@ -79,6 +88,27 @@ namespace Squidex.Infrastructure.Commands .MustHaveHappened(); } + [Fact] + public async Task Create_synced_with_task_should_create_domain_object_and_save() + { + MyDomainObject passedDomainObject = null; + + await sut.CreateSyncedAsync(context, async x => + { + x.RaiseEvent(new MyEvent()); + + await Task.Yield(); + + passedDomainObject = x; + }); + + Assert.Equal(domainObject, passedDomainObject); + Assert.NotNull(context.Result>()); + + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) + .MustHaveHappened(); + } + [Fact] public async Task Create_should_create_domain_object_and_save() { @@ -98,12 +128,37 @@ namespace Squidex.Infrastructure.Commands .MustHaveHappened(); } + [Fact] + public async Task Create_synced_should_create_domain_object_and_save() + { + MyDomainObject passedDomainObject = null; + + await sut.CreateSyncedAsync(context, x => + { + x.RaiseEvent(new MyEvent()); + + passedDomainObject = x; + }); + + Assert.Equal(domainObject, passedDomainObject); + Assert.NotNull(context.Result>()); + + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) + .MustHaveHappened(); + } + [Fact] public Task Update_with_task_should_throw_exception_if_not_aggregate_command() { return Assert.ThrowsAnyAsync(() => sut.UpdateAsync(new CommandContext(A.Dummy()), x => TaskHelper.False)); } + [Fact] + public Task Update_synced_with_task_should_throw_exception_if_not_aggregate_command() + { + return Assert.ThrowsAnyAsync(() => sut.UpdateSyncedAsync(new CommandContext(A.Dummy()), x => TaskHelper.False)); + } + [Fact] public async Task Update_with_task_should_create_domain_object_and_save() { @@ -125,6 +180,27 @@ namespace Squidex.Infrastructure.Commands .MustHaveHappened(); } + [Fact] + public async Task Update_synced_with_task_should_create_domain_object_and_save() + { + MyDomainObject passedDomainObject = null; + + await sut.UpdateSyncedAsync(context, async x => + { + x.RaiseEvent(new MyEvent()); + + await Task.Yield(); + + passedDomainObject = x; + }); + + Assert.Equal(domainObject, passedDomainObject); + Assert.NotNull(context.Result()); + + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) + .MustHaveHappened(); + } + [Fact] public async Task Update_should_create_domain_object_and_save() { @@ -143,5 +219,24 @@ namespace Squidex.Infrastructure.Commands A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) .MustHaveHappened(); } + + [Fact] + public async Task Update_synced_should_create_domain_object_and_save() + { + MyDomainObject passedDomainObject = null; + + await sut.UpdateSyncedAsync(context, x => + { + x.RaiseEvent(new MyEvent()); + + passedDomainObject = x; + }); + + Assert.Equal(domainObject, passedDomainObject); + Assert.NotNull(context.Result()); + + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) + .MustHaveHappened(); + } } } diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs index 1915c6a08..4a081616d 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs @@ -229,6 +229,27 @@ namespace Squidex.Infrastructure.EventSourcing.Grains .MustNotHaveHappened(); } + [Fact] + public async Task Should_stop_if_consumer_failed() + { + sut.ActivateAsync(consumerName, store).Wait(); + sut.Activate(eventConsumer); + + var ex = new InvalidOperationException(); + + await OnErrorAsync(eventSubscription, ex); + + sut.Dispose(); + + state.ShouldBeEquivalentTo(new EventConsumerState { IsStopped = true, Position = initialPosition, Error = ex.ToString() }); + + A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored, -1)) + .MustHaveHappened(Repeated.Exactly.Once); + + A.CallTo(() => eventSubscription.StopAsync()) + .MustHaveHappened(Repeated.Exactly.Once); + } + [Fact] public async Task Should_not_make_error_handling_when_exception_is_from_another_subscription() { diff --git a/tests/Squidex.Infrastructure.Tests/LanguageTests.cs b/tests/Squidex.Infrastructure.Tests/LanguageTests.cs index c4b900f36..98e95a985 100644 --- a/tests/Squidex.Infrastructure.Tests/LanguageTests.cs +++ b/tests/Squidex.Infrastructure.Tests/LanguageTests.cs @@ -96,6 +96,18 @@ namespace Squidex.Infrastructure Assert.Equal(language, Language.GetLanguage(languageCode)); } + [Theory] + [InlineData("en-US", "en")] + [InlineData("en-GB", "en")] + [InlineData("EN-US", "en")] + [InlineData("EN-GB", "en")] + public void Should_parse_lanuages_from_culture(string input, string languageCode) + { + var language = Language.ParseOrNull(input); + + Assert.Equal(language, Language.GetLanguage(languageCode)); + } + [Theory] [InlineData("")] [InlineData(" ")] diff --git a/tests/Squidex.Infrastructure.Tests/Reflection/SimpleCopierTests.cs b/tests/Squidex.Infrastructure.Tests/Reflection/SimpleCopierTests.cs index f6a59a82c..90f86cf37 100644 --- a/tests/Squidex.Infrastructure.Tests/Reflection/SimpleCopierTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Reflection/SimpleCopierTests.cs @@ -37,13 +37,24 @@ namespace Squidex.Infrastructure.Reflection { public int Value2 { get; set; } + public int ValueReadOnly { get; } + public Cloneable Cloneable { get; set; } + + public MyClass1() + { + } + + public MyClass1(int readValue) + { + ValueReadOnly = readValue; + } } [Fact] public void Should_copy_class() { - var value = new MyClass1 + var value = new MyClass1(100) { Value1 = 1, Value2 = 2, @@ -54,6 +65,7 @@ namespace Squidex.Infrastructure.Reflection Assert.Equal(value.Value1, copy.Value1); Assert.Equal(value.Value2, copy.Value2); + Assert.Equal(0, copy.ValueReadOnly); Assert.Equal(value.Cloneable.Value, copy.Cloneable.Value); Assert.NotSame(value.Cloneable, copy.Cloneable); diff --git a/tests/Squidex.Infrastructure.Tests/States/StateSnapshotTests.cs b/tests/Squidex.Infrastructure.Tests/States/StateSnapshotTests.cs index bb45d0cac..cbca0905b 100644 --- a/tests/Squidex.Infrastructure.Tests/States/StateSnapshotTests.cs +++ b/tests/Squidex.Infrastructure.Tests/States/StateSnapshotTests.cs @@ -43,9 +43,9 @@ namespace Squidex.Infrastructure.States state = value; } - public Task WriteStateAsync() + public Task WriteStateAsync(long newVersion = -1) { - return persistence.WriteSnapshotAsync(state); + return persistence.WriteSnapshotAsync(state, newVersion); } } @@ -173,8 +173,6 @@ namespace Squidex.Infrastructure.States { statefulObject.ExpectedVersion = null; - var version = 1; - InvalidateMessage message = null; pubSub.Subscribe(m => @@ -183,7 +181,7 @@ namespace Squidex.Infrastructure.States }); A.CallTo(() => snapshotStore.ReadAsync(key)) - .Returns((123, version)); + .Returns((123, 13)); var actualObject = await sut.GetSingleAsync(key); @@ -194,7 +192,7 @@ namespace Squidex.Infrastructure.States await statefulObject.WriteStateAsync(); - A.CallTo(() => snapshotStore.WriteAsync(key, 456, version, 2)) + A.CallTo(() => snapshotStore.WriteAsync(key, 456, 13, 14)) .MustHaveHappened(); Assert.NotNull(message); @@ -202,16 +200,35 @@ namespace Squidex.Infrastructure.States } [Fact] - public async Task Should_wrap_exception_when_writing_to_store_with_previous_version() + public async Task Should_write_to_store_with_explicit_version() { statefulObject.ExpectedVersion = null; - var version = 1; + A.CallTo(() => snapshotStore.ReadAsync(key)) + .Returns((123, 1)); + + var actualObject = await sut.GetSingleAsync(key); + + Assert.Same(statefulObject, actualObject); + Assert.Equal(123, statefulObject.State); + + statefulObject.SetState(456); + + await statefulObject.WriteStateAsync(100); + + A.CallTo(() => snapshotStore.WriteAsync(key, 456, 1, 100)) + .MustHaveHappened(); + } + + [Fact] + public async Task Should_wrap_exception_when_writing_to_store_with_previous_version() + { + statefulObject.ExpectedVersion = null; A.CallTo(() => snapshotStore.ReadAsync(key)) - .Returns((123, version)); + .Returns((123, 13)); - A.CallTo(() => snapshotStore.WriteAsync(key, 123, version, 2)) + A.CallTo(() => snapshotStore.WriteAsync(key, 123, 13, 14)) .Throws(new InconsistentStateException(1, 1, new InvalidOperationException())); var actualObject = await sut.GetSingleAsync(key);