diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs index 46fbc77b5..0716b309e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs @@ -319,10 +319,14 @@ namespace Squidex.Domain.Apps.Entities.Apps var events = new List { CreateInitalEvent(command.Name), - CreateInitialOwner(command.Actor), CreateInitialLanguage() }; + if (command.Actor.IsSubject) + { + events.Add(CreateInitialOwner(command.Actor)); + } + foreach (var (key, value) in initialPatterns) { events.Add(CreateInitialPattern(key, value)); diff --git a/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs b/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs index 4cc9876a5..9733878e5 100644 --- a/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs +++ b/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs @@ -83,6 +83,8 @@ namespace Squidex.Infrastructure.Commands public async sealed override Task RebuildStateAsync() { + await EnsureLoadedAsync(); + if (persistence != null) { await persistence.WriteSnapshotAsync(Snapshot); diff --git a/backend/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs b/backend/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs index b43b96312..39ed02135 100644 --- a/backend/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs +++ b/backend/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs @@ -176,6 +176,8 @@ namespace Squidex.Infrastructure.Commands } } + isLoaded = true; + return result; } catch diff --git a/backend/src/Squidex.Infrastructure/Commands/LogSnapshotDomainObject.cs b/backend/src/Squidex.Infrastructure/Commands/LogSnapshotDomainObject.cs index 427a74739..51cab2049 100644 --- a/backend/src/Squidex.Infrastructure/Commands/LogSnapshotDomainObject.cs +++ b/backend/src/Squidex.Infrastructure/Commands/LogSnapshotDomainObject.cs @@ -97,6 +97,8 @@ namespace Squidex.Infrastructure.Commands public async sealed override Task RebuildStateAsync() { + await EnsureLoadedAsync(); + if (persistence != null) { var persistedSnapshots = store.GetSnapshotStore(); diff --git a/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs b/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs index 2c2bb583a..8b0c8ea82 100644 --- a/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs +++ b/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs @@ -75,7 +75,6 @@ namespace Squidex.Infrastructure.Commands domainObject.Setup(id); - await domainObject.EnsureLoadedAsync(); await domainObject.RebuildStateAsync(); } catch (DomainObjectNotFoundException) diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs index 60196c735..43748f092 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs @@ -90,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Apps [Fact] public async Task Create_should_create_events_and_update_state() { - var command = new CreateApp { Name = AppName, Actor = Actor, AppId = AppId }; + var command = new CreateApp { Name = AppName, AppId = AppId }; var result = await PublishAsync(command); @@ -107,6 +107,25 @@ namespace Squidex.Domain.Apps.Entities.Apps ); } + [Fact] + public async Task Create_should_not_assign_client_as_contributor() + { + var command = new CreateApp { Name = AppName, Actor = ActorClient, AppId = AppId }; + + var result = await PublishAsync(command); + + result.ShouldBeEquivalent2(sut.Snapshot); + + Assert.Equal(AppName, sut.Snapshot.Name); + + LastEvents + .ShouldHaveSameEvents( + CreateEvent(new AppCreated { Name = AppName }, true), + CreateEvent(new AppPatternAdded { PatternId = patternId1, Name = "Number", Pattern = "[0-9]" }, true), + CreateEvent(new AppPatternAdded { PatternId = patternId2, Name = "Numbers", Pattern = "[0-9]*" }, true) + ); + } + [Fact] public async Task Update_should_create_events_and_update_state() { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs index 709cf26af..e1ce5ac10 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs @@ -27,6 +27,8 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers protected RefToken Actor { get; } = new RefToken(RefTokenType.Subject, "me"); + protected RefToken ActorClient { get; } = new RefToken(RefTokenType.Client, "client"); + protected Guid AppId { get; } = Guid.NewGuid(); protected Guid SchemaId { get; } = Guid.NewGuid(); @@ -85,7 +87,7 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers command.Actor = Actor; } - if (command.User == null) + if (command.User == null && command.Actor.IsSubject) { command.User = User; } @@ -108,30 +110,21 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers return command.AsJ(); } - protected TEvent CreateEvent(TEvent @event) where TEvent : SquidexEvent + protected TEvent CreateEvent(TEvent @event, bool fromClient = false) where TEvent : SquidexEvent { - @event.Actor = Actor; - - EnrichAppInfo(@event); - EnrichSchemaInfo(@event); - - return @event; - } + @event.Actor = fromClient ? ActorClient : Actor; - private void EnrichAppInfo(IEvent @event) - { if (@event is AppEvent appEvent) { appEvent.AppId = AppNamedId; } - } - private void EnrichSchemaInfo(IEvent @event) - { if (@event is SchemaEvent schemaEvent) { schemaEvent.SchemaId = SchemaNamedId; } + + return @event; } } } \ No newline at end of file diff --git a/backend/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectTests.cs index 63e726cfe..c5d9f21bc 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectTests.cs @@ -71,9 +71,6 @@ namespace Squidex.Infrastructure.Commands public DomainObjectTests() { - A.CallTo(() => store.WithSnapshotsAndEventSourcing(typeof(MyDomainObject), id, A>.Ignored, A.Ignored)) - .Returns(persistence); - sut = new MyDomainObject(store); } @@ -86,7 +83,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_write_state_and_events_when_created() { - await SetupEmptyAsync(); + SetupEmpty(); var result = await sut.ExecuteAsync(new CreateAuto { Value = 4 }); @@ -94,6 +91,8 @@ namespace Squidex.Infrastructure.Commands .MustHaveHappened(); A.CallTo(() => persistence.WriteEventsAsync(A>>.That.Matches(x => x.Count() == 1))) .MustHaveHappened(); + A.CallTo(() => persistence.ReadAsync(A.Ignored)) + .MustNotHaveHappened(); Assert.True(result is EntityCreatedResult); @@ -103,17 +102,43 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(0, sut.Snapshot.Version); } + [Fact] + public async Task Should_write_state_and_events_when_updated_after_creation() + { + SetupEmpty(); + + await sut.ExecuteAsync(new CreateAuto { Value = 4 }); + + var result = await sut.ExecuteAsync(new UpdateAuto { Value = 8, ExpectedVersion = 0 }); + + A.CallTo(() => persistence.WriteSnapshotAsync(A.That.Matches(x => x.Value == 8))) + .MustHaveHappened(); + A.CallTo(() => persistence.WriteEventsAsync(A>>.That.Matches(x => x.Count() == 1))) + .MustHaveHappened(); + A.CallTo(() => persistence.ReadAsync(A.Ignored)) + .MustNotHaveHappened(); + + Assert.True(result is EntitySavedResult); + + Assert.Empty(sut.GetUncomittedEvents()); + + Assert.Equal(8, sut.Snapshot.Value); + Assert.Equal(1, sut.Snapshot.Version); + } + [Fact] public async Task Should_write_state_and_events_when_updated() { - await SetupCreatedAsync(); + SetupCreated(4); - var result = await sut.ExecuteAsync(new UpdateAuto { Value = 8 }); + var result = await sut.ExecuteAsync(new UpdateAuto { Value = 8, ExpectedVersion = 0 }); A.CallTo(() => persistence.WriteSnapshotAsync(A.That.Matches(x => x.Value == 8))) .MustHaveHappened(); A.CallTo(() => persistence.WriteEventsAsync(A>>.That.Matches(x => x.Count() == 1))) .MustHaveHappened(); + A.CallTo(() => persistence.ReadAsync(A.Ignored)) + .MustHaveHappenedOnceExactly(); Assert.True(result is EntitySavedResult); @@ -123,25 +148,38 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(1, sut.Snapshot.Version); } + [Fact] + public async Task Should_only_load_once_on_update() + { + SetupCreated(4); + + await sut.ExecuteAsync(new UpdateAuto { Value = 8, ExpectedVersion = 0 }); + await sut.ExecuteAsync(new UpdateAuto { Value = 9, ExpectedVersion = 1 }); + + A.CallTo(() => persistence.ReadAsync(A.Ignored)) + .MustHaveHappenedOnceExactly(); + + Assert.Equal(9, sut.Snapshot.Value); + Assert.Equal(2, sut.Snapshot.Version); + } + [Fact] public async Task Should_rebuild_state_async() { - await SetupCreatedAsync(); + SetupCreated(4); await sut.RebuildStateAsync(); A.CallTo(() => persistence.WriteSnapshotAsync(A.That.Matches(x => x.Value == 4))) .MustHaveHappened(); A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) - .MustHaveHappenedOnceExactly(); + .MustNotHaveHappened(); } [Fact] public async Task Should_not_update_when_snapshot_is_not_changed() { - await SetupCreatedAsync(); - - var previousSnapshot = sut.Snapshot; + SetupCreated(4); var result = await sut.ExecuteAsync(new UpdateAuto { Value = MyDomainState.Unchanged }); @@ -151,14 +189,12 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(4, sut.Snapshot.Value); Assert.Equal(0, sut.Snapshot.Version); - - Assert.Same(previousSnapshot, sut.Snapshot); } [Fact] public async Task Should_not_throw_exception_when_already_created() { - await SetupCreatedAsync(); + SetupCreated(4); await sut.ExecuteAsync(new CreateAuto()); } @@ -166,7 +202,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_throw_exception_when_not_created() { - await SetupEmptyAsync(); + SetupEmpty(); await Assert.ThrowsAsync(() => sut.ExecuteAsync(new UpdateAuto())); } @@ -174,7 +210,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_return_custom_result_on_create() { - await SetupEmptyAsync(); + SetupEmpty(); var result = await sut.ExecuteAsync(new CreateCustom()); @@ -184,7 +220,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_return_custom_result_on_update() { - await SetupCreatedAsync(); + SetupCreated(4); var result = await sut.ExecuteAsync(new UpdateCustom()); @@ -194,7 +230,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_throw_exception_when_other_verison_expected() { - await SetupCreatedAsync(); + SetupCreated(4); await Assert.ThrowsAsync(() => sut.ExecuteAsync(new UpdateCustom { ExpectedVersion = 3 })); } @@ -202,7 +238,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_reset_state_when_writing_snapshot_for_create_failed() { - await SetupEmptyAsync(); + SetupEmpty(); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) .Throws(new InvalidOperationException()); @@ -218,7 +254,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_reset_state_when_writing_snapshot_for_update_failed() { - await SetupCreatedAsync(); + SetupCreated(4); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) .Throws(new InvalidOperationException()); @@ -231,18 +267,42 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(0, sut.Snapshot.Version); } - private async Task SetupCreatedAsync() + private void SetupCreated(int value) { - sut.Setup(id); + HandleEvent handleEvent = x => { }; - await sut.ExecuteAsync(new CreateAuto { Value = 4 }); + var version = -1; + + A.CallTo(() => persistence.ReadAsync(-2)) + .Invokes(() => + { + version = 0; + + handleEvent(Envelope.Create(new ValueChanged { Value = value })); + }); + + A.CallTo(() => store.WithSnapshotsAndEventSourcing(typeof(MyDomainObject), id, A>.Ignored, A.Ignored)) + .Invokes(args => + { + handleEvent = args.GetArgument(3); + }) + .Returns(persistence); + + A.CallTo(() => persistence.Version) + .ReturnsLazily(() => version); + + sut.Setup(id); } - private async Task SetupEmptyAsync() + private void SetupEmpty() { - sut.Setup(id); + A.CallTo(() => store.WithSnapshotsAndEventSourcing(typeof(MyDomainObject), id, A>.Ignored, A.Ignored)) + .Returns(persistence); + + A.CallTo(() => persistence.Version) + .Returns(-1); - await Task.Yield(); + sut.Setup(id); } } } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Commands/LogSnapshotDomainObjectTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Commands/LogSnapshotDomainObjectTests.cs index e21aa16d4..df3ca10b8 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Commands/LogSnapshotDomainObjectTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Commands/LogSnapshotDomainObjectTests.cs @@ -73,9 +73,6 @@ namespace Squidex.Infrastructure.Commands public LogSnapshotDomainObjectTests() { - A.CallTo(() => store.WithEventSourcing(typeof(MyLogDomainObject), id, A.Ignored)) - .Returns(persistence); - A.CallTo(() => store.GetSnapshotStore()) .Returns(snapshotStore); @@ -85,7 +82,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_get_latestet_version_when_requesting_state_with_any() { - await SetupUpdatedAsync(); + await SetupUpdatedAsync(4, 8); var result = sut.GetSnapshot(EtagVersion.Any); @@ -95,7 +92,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_get_latestet_version_when_requesting_state_with_auto() { - await SetupUpdatedAsync(); + await SetupUpdatedAsync(4, 8); var result = sut.GetSnapshot(EtagVersion.Auto); @@ -105,7 +102,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_get_empty_version_when_requesting_state_with_empty_version() { - await SetupUpdatedAsync(); + await SetupUpdatedAsync(4, 8); var result = sut.GetSnapshot(EtagVersion.Empty); @@ -115,7 +112,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_get_specific_version_when_requesting_state_with_specific_version() { - await SetupUpdatedAsync(); + await SetupUpdatedAsync(4, 8); sut.GetSnapshot(0).Should().BeEquivalentTo(new MyDomainState { Value = 4, Version = 0 }); sut.GetSnapshot(1).Should().BeEquivalentTo(new MyDomainState { Value = 8, Version = 1 }); @@ -124,7 +121,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_get_null_state_when_requesting_state_with_invalid_version() { - await SetupUpdatedAsync(); + await SetupUpdatedAsync(4, 8); Assert.Null(sut.GetSnapshot(-4)); Assert.Null(sut.GetSnapshot(2)); @@ -139,7 +136,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_write_state_and_events_when_created() { - await SetupEmptyAsync(); + SetupEmpty(); var result = await sut.ExecuteAsync(new CreateAuto { Value = 4 }); @@ -147,6 +144,8 @@ namespace Squidex.Infrastructure.Commands .MustHaveHappened(); A.CallTo(() => persistence.WriteEventsAsync(A>>.That.Matches(x => x.Count() == 1))) .MustHaveHappened(); + A.CallTo(() => persistence.ReadAsync(A.Ignored)) + .MustNotHaveHappened(); Assert.True(result is EntityCreatedResult); @@ -156,17 +155,43 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(0, sut.Snapshot.Version); } + [Fact] + public async Task Should_write_state_and_events_when_updated_after_creation() + { + SetupEmpty(); + + await sut.ExecuteAsync(new CreateAuto { Value = 4 }); + + var result = await sut.ExecuteAsync(new UpdateAuto { Value = 8, ExpectedVersion = 0 }); + + A.CallTo(() => snapshotStore.WriteAsync(id, A.That.Matches(x => x.Value == 8), 0, 1)) + .MustHaveHappened(); + A.CallTo(() => persistence.WriteEventsAsync(A>>.That.Matches(x => x.Count() == 1))) + .MustHaveHappened(); + A.CallTo(() => persistence.ReadAsync(A.Ignored)) + .MustNotHaveHappened(); + + Assert.True(result is EntitySavedResult); + + Assert.Empty(sut.GetUncomittedEvents()); + + Assert.Equal(8, sut.Snapshot.Value); + Assert.Equal(1, sut.Snapshot.Version); + } + [Fact] public async Task Should_write_state_and_events_when_updated() { - await SetupCreatedAsync(); + SetupCreated(4); - var result = await sut.ExecuteAsync(new UpdateAuto { Value = 8 }); + var result = await sut.ExecuteAsync(new UpdateAuto { Value = 8, ExpectedVersion = 0 }); A.CallTo(() => snapshotStore.WriteAsync(id, A.That.Matches(x => x.Value == 8), 0, 1)) .MustHaveHappened(); A.CallTo(() => persistence.WriteEventsAsync(A>>.That.Matches(x => x.Count() == 1))) .MustHaveHappened(); + A.CallTo(() => persistence.ReadAsync(A.Ignored)) + .MustHaveHappenedOnceExactly(); Assert.True(result is EntitySavedResult); @@ -176,25 +201,38 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(1, sut.Snapshot.Version); } + [Fact] + public async Task Should_only_load_once_on_update() + { + SetupCreated(4); + + await sut.ExecuteAsync(new UpdateAuto { Value = 8, ExpectedVersion = 0 }); + await sut.ExecuteAsync(new UpdateAuto { Value = 9, ExpectedVersion = 1 }); + + A.CallTo(() => persistence.ReadAsync(A.Ignored)) + .MustHaveHappenedOnceExactly(); + + Assert.Equal(9, sut.Snapshot.Value); + Assert.Equal(2, sut.Snapshot.Version); + } + [Fact] public async Task Should_rebuild_state_async() { - await SetupCreatedAsync(); + SetupCreated(4); await sut.RebuildStateAsync(); A.CallTo(() => snapshotStore.WriteAsync(id, A.That.Matches(x => x.Value == 4), EtagVersion.Any, 0)) .MustHaveHappened(); A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) - .MustHaveHappenedOnceExactly(); + .MustNotHaveHappened(); } [Fact] public async Task Should_not_update_when_snapshot_is_not_changed() { - await SetupCreatedAsync(); - - var previousSnapshot = sut.Snapshot; + SetupCreated(4); var result = await sut.ExecuteAsync(new UpdateAuto { Value = MyDomainState.Unchanged }); @@ -204,14 +242,12 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(4, sut.Snapshot.Value); Assert.Equal(0, sut.Snapshot.Version); - - Assert.Same(previousSnapshot, sut.Snapshot); } [Fact] public async Task Should_not_throw_exception_when_already_created() { - await SetupCreatedAsync(); + SetupCreated(4); await sut.ExecuteAsync(new CreateAuto()); } @@ -219,7 +255,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_throw_exception_when_not_created() { - await SetupEmptyAsync(); + SetupEmpty(); await Assert.ThrowsAsync(() => sut.ExecuteAsync(new UpdateAuto())); } @@ -227,7 +263,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_return_custom_result_on_create() { - await SetupEmptyAsync(); + SetupEmpty(); var result = await sut.ExecuteAsync(new CreateCustom()); @@ -237,7 +273,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_return_custom_result_on_update() { - await SetupCreatedAsync(); + SetupCreated(4); var result = await sut.ExecuteAsync(new UpdateCustom()); @@ -247,7 +283,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_throw_exception_when_other_verison_expected() { - await SetupCreatedAsync(); + SetupCreated(4); await Assert.ThrowsAsync(() => sut.ExecuteAsync(new UpdateCustom { ExpectedVersion = 3 })); } @@ -255,7 +291,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_reset_state_when_writing_snapshot_for_create_failed() { - await SetupEmptyAsync(); + SetupEmpty(); A.CallTo(() => snapshotStore.WriteAsync(A.Ignored, A.Ignored, -1, 0)) .Throws(new InvalidOperationException()); @@ -271,7 +307,7 @@ namespace Squidex.Infrastructure.Commands [Fact] public async Task Should_reset_state_when_writing_snapshot_for_update_failed() { - await SetupCreatedAsync(); + SetupCreated(4); A.CallTo(() => snapshotStore.WriteAsync(A.Ignored, A.Ignored, 0, 1)) .Throws(new InvalidOperationException()); @@ -284,25 +320,49 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(0, sut.Snapshot.Version); } - private async Task SetupCreatedAsync() + private async Task SetupUpdatedAsync(int value, int updatedValue) { - sut.Setup(id); + SetupCreated(value); - await sut.ExecuteAsync(new CreateAuto { Value = 4 }); + await sut.ExecuteAsync(new UpdateAuto { Value = updatedValue }); } - private async Task SetupUpdatedAsync() + private void SetupCreated(int value) { - await SetupCreatedAsync(); + HandleEvent handleEvent = x => { }; + + var version = -1; + + A.CallTo(() => persistence.ReadAsync(-2)) + .Invokes(() => + { + version = 0; - await sut.ExecuteAsync(new UpdateAuto { Value = 8 }); + handleEvent(Envelope.Create(new ValueChanged { Value = value })); + }); + + A.CallTo(() => store.WithEventSourcing(typeof(MyLogDomainObject), id, A.Ignored)) + .Invokes(args => + { + handleEvent = args.GetArgument(2); + }) + .Returns(persistence); + + A.CallTo(() => persistence.Version) + .ReturnsLazily(() => version); + + sut.Setup(id); } - private async Task SetupEmptyAsync() + private void SetupEmpty() { - sut.Setup(id); + A.CallTo(() => store.WithEventSourcing(typeof(MyLogDomainObject), id, A.Ignored)) + .Returns(persistence); + + A.CallTo(() => persistence.Version) + .Returns(-1); - await Task.Yield(); + sut.Setup(id); } } } diff --git a/frontend/app/shared/components/comments.component.ts b/frontend/app/shared/components/comments.component.ts index 924459fff..183e7efda 100644 --- a/frontend/app/shared/components/comments.component.ts +++ b/frontend/app/shared/components/comments.component.ts @@ -9,7 +9,7 @@ import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { Router } from '@angular/router'; import { timer } from 'rxjs'; -import { map, onErrorResumeNext, switchMap } from 'rxjs/operators'; +import { filter, map, onErrorResumeNext, switchMap } from 'rxjs/operators'; import { AppsState, @@ -36,7 +36,7 @@ export class CommentsComponent extends ResourceOwner implements OnInit { public commentsState: CommentsState; public commentForm = new UpsertCommentForm(this.formBuilder); - public mentionUsers = this.contributorsState.contributors.pipe(map(x => x.map(c => c.contributorEmail))); + public mentionUsers = this.contributorsState.contributors.pipe(map(x => x.map(c => c.contributorEmail), filter(x => !!x))); public mentionConfig = { dropUp: true }; public userToken: string;