diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/StatusChange.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/StatusChange.cs new file mode 100644 index 000000000..9e3900deb --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/StatusChange.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.Contents +{ + public enum StatusChange + { + Archived, + Published, + Restored, + Unpublished + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs index 62961abc4..3ffb37205 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs @@ -334,9 +334,9 @@ namespace Squidex.Domain.Apps.Entities.Apps return new AppContributorAssigned { ContributorId = actor.Identifier, Permission = AppContributorPermission.Owner }; } - public override void ApplyEvent(Envelope @event) + protected override AppState OnEvent(Envelope @event) { - ApplySnapshot(Snapshot.Apply(@event)); + return Snapshot.Apply(@event); } public Task> GetStateAsync() diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs index 969d0852e..70955e1bc 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs @@ -135,9 +135,9 @@ namespace Squidex.Domain.Apps.Entities.Assets } } - public override void ApplyEvent(Envelope @event) + protected override AssetState OnEvent(Envelope @event) { - ApplySnapshot(Snapshot.Apply(@event)); + return Snapshot.Apply(@event); } public Task> GetStateAsync() diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs index 9e2698cdb..5b16c29ec 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs @@ -292,9 +292,9 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - public override void ApplyEvent(Envelope @event) + protected override ContentState OnEvent(Envelope @event) { - ApplySnapshot(Snapshot.Apply(@event)); + return Snapshot.Apply(@event); } private async Task CreateContext(Guid appId, Guid schemaId, Func message) diff --git a/src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs index 81e475e4d..92cdd4f95 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleGrain.cs @@ -123,9 +123,9 @@ namespace Squidex.Domain.Apps.Entities.Rules } } - public override void ApplyEvent(Envelope @event) + protected override RuleState OnEvent(Envelope @event) { - ApplySnapshot(Snapshot.Apply(@event)); + return Snapshot.Apply(@event); } public Task> GetStateAsync() diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs index 0939f5332..6fc848281 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs @@ -360,9 +360,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas } } - public override void ApplyEvent(Envelope @event) + protected override SchemaState OnEvent(Envelope @event) { - ApplySnapshot(Snapshot.Apply(@event, registry)); + return Snapshot.Apply(@event, registry); } public Task> GetStateAsync() diff --git a/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs b/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs index 3036d25dc..9dd94274f 100644 --- a/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs +++ b/src/Squidex.Infrastructure/Commands/DomainObjectGrain.cs @@ -6,220 +6,59 @@ // ========================================================================== using System; -using System.Collections.Generic; using System.Threading.Tasks; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Log; -using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.States; -using Squidex.Infrastructure.Tasks; namespace Squidex.Infrastructure.Commands { - public abstract class DomainObjectGrain : GrainOfGuid, IDomainObjectGrain where T : IDomainState, new() + public abstract class DomainObjectGrain : DomainObjectGrainBase where T : IDomainState, new() { - private readonly List> uncomittedEvents = new List>(); private readonly IStore store; - private readonly ISemanticLog log; - private Guid id; private T snapshot = new T { Version = EtagVersion.Empty }; private IPersistence persistence; - public Guid Id - { - get { return id; } - } - - public long Version - { - get { return snapshot.Version; } - } - - public long NewVersion - { - get { return snapshot.Version + uncomittedEvents.Count; } - } - - public T Snapshot + public override T Snapshot { get { return snapshot; } } protected DomainObjectGrain(IStore store, ISemanticLog log) + : base(log) { Guard.NotNull(store, nameof(store)); - Guard.NotNull(log, nameof(log)); this.store = store; - - this.log = log; } - public override async Task OnActivateAsync(Guid key) + protected sealed override void ApplyEvent(Envelope @event) { - using (log.MeasureInformation(w => w - .WriteProperty("action", "ActivateDomainObject") - .WriteProperty("domainObjectType", GetType().Name) - .WriteProperty("domainObjectKey", key.ToString()))) - { - id = key; - - persistence = store.WithSnapshotsAndEventSourcing(GetType(), id, ApplySnapshot, ApplyEvent); - - await persistence.ReadAsync(); - } + snapshot = OnEvent(@event); + snapshot.Version = NewVersion + 1; } - public void RaiseEvent(IEvent @event) + protected sealed override void RestorePreviousSnapshot(T previousSnapshot, long previousVersion) { - RaiseEvent(Envelope.Create(@event)); + snapshot = previousSnapshot; } - public virtual void RaiseEvent(Envelope @event) + protected sealed override Task ReadAsync(Type type, Guid id) { - Guard.NotNull(@event, nameof(@event)); - - @event.SetAggregateId(id); + persistence = store.WithSnapshotsAndEventSourcing(GetType(), id, x => snapshot = x, ApplyEvent); - ApplyEvent(@event); - - uncomittedEvents.Add(@event); + return persistence.ReadAsync(); } - public IReadOnlyList> GetUncomittedEvents() + protected sealed override async Task WriteAsync(Envelope[] events, long previousVersion) { - return uncomittedEvents; - } - - public void ClearUncommittedEvents() - { - uncomittedEvents.Clear(); - } - - public virtual void ApplySnapshot(T newSnapshot) - { - snapshot = newSnapshot; - } - - public virtual void ApplyEvent(Envelope @event) - { - } - - protected Task CreateReturnAsync(TCommand command, Func> handler) where TCommand : class, IAggregateCommand - { - return InvokeAsync(command, handler, false); - } - - protected Task CreateReturnAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand - { - return InvokeAsync(command, handler?.ToAsync(), false); - } - - protected Task CreateAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand - { - return InvokeAsync(command, handler.ToDefault(), false); - } - - protected Task CreateAsync(TCommand command, Action handler) where TCommand : class, IAggregateCommand - { - return InvokeAsync(command, handler?.ToDefault()?.ToAsync(), false); - } - - protected Task UpdateReturnAsync(TCommand command, Func> handler) where TCommand : class, IAggregateCommand - { - return InvokeAsync(command, handler, true); - } - - protected Task UpdateReturnAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand - { - return InvokeAsync(command, handler?.ToAsync(), true); - } - - protected Task UpdateAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand - { - return InvokeAsync(command, handler?.ToDefault(), true); - } - - protected Task UpdateAsync(TCommand command, Action handler) where TCommand : class, IAggregateCommand - { - return InvokeAsync(command, handler?.ToDefault()?.ToAsync(), true); - } - - private async Task InvokeAsync(TCommand command, Func> handler, bool isUpdate) where TCommand : class, IAggregateCommand - { - Guard.NotNull(command, nameof(command)); - - if (command.ExpectedVersion != EtagVersion.Any && command.ExpectedVersion != Version) - { - throw new DomainObjectVersionException(id.ToString(), GetType(), Version, command.ExpectedVersion); - } - - if (isUpdate && Version < 0) - { - try - { - DeactivateOnIdle(); - } - catch (InvalidOperationException) - { - } - - throw new DomainObjectNotFoundException(id.ToString(), GetType()); - } - - if (!isUpdate && Version >= 0) - { - throw new DomainException("Object has already been created."); - } - - var previousSnapshot = snapshot; - try + if (events.Length > 0) { - var result = await handler(command); - - var events = uncomittedEvents.ToArray(); - - if (events.Length > 0) - { - snapshot.Version = NewVersion; - - await persistence.WriteEventsAsync(events); - await persistence.WriteSnapshotAsync(snapshot); - } - - if (result == null) - { - if (isUpdate) - { - result = new EntitySavedResult(Version); - } - else - { - result = EntityCreatedResult.Create(id, Version); - } - } - - return result; + await persistence.WriteEventsAsync(events); + await persistence.WriteSnapshotAsync(Snapshot); } - catch - { - snapshot = previousSnapshot; - - throw; - } - finally - { - uncomittedEvents.Clear(); - } - } - - public async Task> ExecuteAsync(J command) - { - var result = await ExecuteAsync(command.Value); - - return result.AsJ(); } - protected abstract Task ExecuteAsync(IAggregateCommand command); + protected abstract T OnEvent(Envelope @event); } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs b/src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs new file mode 100644 index 000000000..1fc5c520f --- /dev/null +++ b/src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs @@ -0,0 +1,207 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Log; +using Squidex.Infrastructure.Orleans; +using Squidex.Infrastructure.Tasks; + +namespace Squidex.Infrastructure.Commands +{ + public abstract class DomainObjectGrainBase : GrainOfGuid, IDomainObjectGrain where T : IDomainState, new() + { + private readonly List> uncomittedEvents = new List>(); + private readonly ISemanticLog log; + private Guid id; + + public Guid Id + { + get { return id; } + } + + public long Version + { + get { return Snapshot.Version; } + } + + public long NewVersion + { + get { return Snapshot.Version + uncomittedEvents.Count; } + } + + public abstract T Snapshot { get; } + + protected DomainObjectGrainBase(ISemanticLog log) + { + Guard.NotNull(log, nameof(log)); + + this.log = log; + } + + public sealed override async Task OnActivateAsync(Guid key) + { + using (log.MeasureInformation(w => w + .WriteProperty("action", "ActivateDomainObject") + .WriteProperty("domainObjectType", GetType().Name) + .WriteProperty("domainObjectKey", key.ToString()))) + { + id = key; + + await ReadAsync(GetType(), id); + } + } + + public void RaiseEvent(IEvent @event) + { + RaiseEvent(Envelope.Create(@event)); + } + + public virtual void RaiseEvent(Envelope @event) + { + Guard.NotNull(@event, nameof(@event)); + + @event.SetAggregateId(id); + + ApplyEvent(@event); + + uncomittedEvents.Add(@event); + } + + public IReadOnlyList> GetUncomittedEvents() + { + return uncomittedEvents; + } + + public void ClearUncommittedEvents() + { + uncomittedEvents.Clear(); + } + + protected Task CreateReturnAsync(TCommand command, Func> handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, handler, false); + } + + protected Task CreateReturnAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, handler?.ToAsync(), false); + } + + protected Task CreateAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, handler.ToDefault(), false); + } + + protected Task CreateAsync(TCommand command, Action handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, handler?.ToDefault()?.ToAsync(), false); + } + + protected Task UpdateReturnAsync(TCommand command, Func> handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, handler, true); + } + + protected Task UpdateReturnAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, handler?.ToAsync(), true); + } + + protected Task UpdateAsync(TCommand command, Func handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, handler?.ToDefault(), true); + } + + protected Task UpdateAsync(TCommand command, Action handler) where TCommand : class, IAggregateCommand + { + return InvokeAsync(command, handler?.ToDefault()?.ToAsync(), true); + } + + private async Task InvokeAsync(TCommand command, Func> handler, bool isUpdate) where TCommand : class, IAggregateCommand + { + Guard.NotNull(command, nameof(command)); + + if (command.ExpectedVersion != EtagVersion.Any && command.ExpectedVersion != Version) + { + throw new DomainObjectVersionException(id.ToString(), GetType(), Version, command.ExpectedVersion); + } + + if (isUpdate && Version < 0) + { + try + { + DeactivateOnIdle(); + } + catch (InvalidOperationException) + { + } + + throw new DomainObjectNotFoundException(id.ToString(), GetType()); + } + + if (!isUpdate && Version >= 0) + { + throw new DomainException("Object has already been created."); + } + + var previousSnapshot = Snapshot; + var previousVersion = Version; + try + { + var result = await handler(command); + + var events = uncomittedEvents.ToArray(); + + await WriteAsync(events, previousVersion); + + if (result == null) + { + if (isUpdate) + { + result = new EntitySavedResult(Version); + } + else + { + result = EntityCreatedResult.Create(id, Version); + } + } + + return result; + } + catch + { + RestorePreviousSnapshot(previousSnapshot, previousVersion); + + throw; + } + finally + { + uncomittedEvents.Clear(); + } + } + + protected abstract void RestorePreviousSnapshot(T previousSnapshot, long previousVersion); + + protected abstract void ApplyEvent(Envelope @event); + + protected abstract Task ReadAsync(Type type, Guid id); + + protected abstract Task WriteAsync(Envelope[] events, long previousVersion); + + public async Task> ExecuteAsync(J command) + { + var result = await ExecuteAsync(command.Value); + + return result.AsJ(); + } + + protected abstract Task ExecuteAsync(IAggregateCommand command); + } +} \ No newline at end of file diff --git a/src/Squidex.Infrastructure/Commands/MultiSnapshotDomainObjectGrain.cs b/src/Squidex.Infrastructure/Commands/MultiSnapshotDomainObjectGrain.cs new file mode 100644 index 000000000..44aa4c731 --- /dev/null +++ b/src/Squidex.Infrastructure/Commands/MultiSnapshotDomainObjectGrain.cs @@ -0,0 +1,93 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Log; +using Squidex.Infrastructure.States; + +namespace Squidex.Infrastructure.Commands +{ + public abstract class MultiSnapshotDomainObjectGrain : DomainObjectGrainBase where T : IDomainState, new() + { + private readonly IStore store; + private readonly List snapshots = new List { new T { Version = EtagVersion.Empty } }; + private IPersistence persistence; + + public override T Snapshot + { + get { return snapshots.Last(); } + } + + protected MultiSnapshotDomainObjectGrain(IStore store, ISemanticLog log) + : base(log) + { + Guard.NotNull(log, nameof(log)); + + this.store = store; + } + + public T GetSnapshot(long version) + { + if (version == EtagVersion.Any) + { + return Snapshot; + } + + if (version == EtagVersion.Empty) + { + return snapshots[0]; + } + + if (version >= 0 && version < snapshots.Count - 1) + { + return snapshots[(int)version + 1]; + } + + return default(T); + } + + protected sealed override void ApplyEvent(Envelope @event) + { + var snapshot = OnEvent(@event); + + snapshot.Version = NewVersion + 1; + snapshots.Add(OnEvent(@event)); + } + + protected sealed override Task ReadAsync(Type type, Guid id) + { + persistence = store.WithEventSourcing(type, id, ApplyEvent); + + return persistence.ReadAsync(); + } + + protected sealed override async Task WriteAsync(Envelope[] events, long previousVersion) + { + if (events.Length > 0) + { + var snaphosts = store.GetSnapshotStore(); + + await persistence.WriteEventsAsync(events); + await snaphosts.WriteAsync(Id, Snapshot, previousVersion, previousVersion + events.Length); + } + } + + protected sealed override void RestorePreviousSnapshot(T previousSnapshot, long previousVersion) + { + while (snapshots.Count > previousVersion) + { + snapshots.RemoveAt(snapshots.Count - 1); + } + } + + protected abstract T OnEvent(Envelope @event); + } +} \ No newline at end of file diff --git a/src/Squidex.Infrastructure/Orleans/GrainOfGuid.cs b/src/Squidex.Infrastructure/Orleans/GrainOfGuid.cs index b014a9c63..0e6e50e22 100644 --- a/src/Squidex.Infrastructure/Orleans/GrainOfGuid.cs +++ b/src/Squidex.Infrastructure/Orleans/GrainOfGuid.cs @@ -14,7 +14,7 @@ namespace Squidex.Infrastructure.Orleans { public abstract class GrainOfGuid : Grain { - public override Task OnActivateAsync() + public sealed override Task OnActivateAsync() { return OnActivateAsync(this.GetPrimaryKey()); } diff --git a/src/Squidex.Infrastructure/Orleans/GrainOfString.cs b/src/Squidex.Infrastructure/Orleans/GrainOfString.cs index 4ed33bacc..3d6b4e089 100644 --- a/src/Squidex.Infrastructure/Orleans/GrainOfString.cs +++ b/src/Squidex.Infrastructure/Orleans/GrainOfString.cs @@ -13,7 +13,7 @@ namespace Squidex.Infrastructure.Orleans { public abstract class GrainOfString : Grain { - public override Task OnActivateAsync() + public sealed override Task OnActivateAsync() { return OnActivateAsync(this.GetPrimaryKeyString()); } diff --git a/src/Squidex.Infrastructure/States/IPersistence.cs b/src/Squidex.Infrastructure/States/IPersistence.cs index c71ff14a2..523a9dd0b 100644 --- a/src/Squidex.Infrastructure/States/IPersistence.cs +++ b/src/Squidex.Infrastructure/States/IPersistence.cs @@ -5,24 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; -using System.Threading.Tasks; -using Squidex.Infrastructure.EventSourcing; - namespace Squidex.Infrastructure.States { public interface IPersistence : IPersistence { } - - public interface IPersistence - { - long Version { get; } - - Task WriteEventsAsync(IEnumerable> @events); - - Task WriteSnapshotAsync(TState state); - - Task ReadAsync(long expectedVersion = EtagVersion.Any); - } } diff --git a/src/Squidex.Infrastructure/States/IPersistence{TState}.cs b/src/Squidex.Infrastructure/States/IPersistence{TState}.cs new file mode 100644 index 000000000..2b467b302 --- /dev/null +++ b/src/Squidex.Infrastructure/States/IPersistence{TState}.cs @@ -0,0 +1,24 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Threading.Tasks; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Infrastructure.States +{ + public interface IPersistence + { + long Version { get; } + + Task WriteEventsAsync(IEnumerable> @events); + + Task WriteSnapshotAsync(TState state); + + Task ReadAsync(long expectedVersion = EtagVersion.Any); + } +} diff --git a/src/Squidex.Infrastructure/States/IStore.cs b/src/Squidex.Infrastructure/States/IStore.cs index e1e1101bd..e0c65ba83 100644 --- a/src/Squidex.Infrastructure/States/IStore.cs +++ b/src/Squidex.Infrastructure/States/IStore.cs @@ -19,6 +19,8 @@ namespace Squidex.Infrastructure.States IPersistence WithSnapshotsAndEventSourcing(Type owner, TKey key, Func applySnapshot, Func, Task> applyEvent); + ISnapshotStore GetSnapshotStore(); + Task ClearSnapshotsAsync(); } } diff --git a/src/Squidex.Infrastructure/States/Store.cs b/src/Squidex.Infrastructure/States/Store.cs index 89e162a54..0a3e02ff7 100644 --- a/src/Squidex.Infrastructure/States/Store.cs +++ b/src/Squidex.Infrastructure/States/Store.cs @@ -44,7 +44,7 @@ namespace Squidex.Infrastructure.States { Guard.NotNull(key, nameof(key)); - var snapshotStore = (ISnapshotStore)services.GetService(typeof(ISnapshotStore)); + var snapshotStore = GetSnapshotStore(); return new Persistence(key, owner, eventStore, eventDataFormatter, snapshotStore, streamNameResolver, applyEvent); } @@ -53,16 +53,19 @@ namespace Squidex.Infrastructure.States { Guard.NotNull(key, nameof(key)); - var snapshotStore = (ISnapshotStore)services.GetService(typeof(ISnapshotStore)); + var snapshotStore = GetSnapshotStore(); return new Persistence(key, owner, eventStore, eventDataFormatter, snapshotStore, streamNameResolver, mode, applySnapshot, applyEvent); } public Task ClearSnapshotsAsync() { - var snapshotStore = (ISnapshotStore)services.GetService(typeof(ISnapshotStore)); + return GetSnapshotStore().ClearAsync(); + } - return snapshotStore.ClearAsync(); + public ISnapshotStore GetSnapshotStore() + { + return (ISnapshotStore)services.GetService(typeof(ISnapshotStore)); } } } diff --git a/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainFormatterTests.cs b/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainFormatterTests.cs new file mode 100644 index 000000000..c12893015 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainFormatterTests.cs @@ -0,0 +1,66 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Reflection; +using FakeItEasy; +using Orleans; +using Squidex.Infrastructure.TestHelpers; +using Xunit; + +namespace Squidex.Infrastructure.Commands +{ + public class DomainObjectGrainFormatterTests + { + private readonly IGrainCallContext context = A.Fake(); + + [Fact] + public void Should_return_fallback_if_no_method_is_defined() + { + A.CallTo(() => context.InterfaceMethod) + .Returns(null); + + var result = DomainObjectGrainFormatter.Format(context); + + Assert.Equal("Unknown", result); + } + + [Fact] + public void Should_return_method_name_if_not_domain_object_method() + { + var methodInfo = A.Fake(); + + A.CallTo(() => methodInfo.Name) + .Returns("Calculate"); + + A.CallTo(() => context.InterfaceMethod) + .Returns(methodInfo); + + var result = DomainObjectGrainFormatter.Format(context); + + Assert.Equal("Calculate", result); + } + + [Fact] + public void Should_return_nice_method_name_if_domain_object_execute() + { + var methodInfo = A.Fake(); + + A.CallTo(() => methodInfo.Name) + .Returns(nameof(IDomainObjectGrain.ExecuteAsync)); + + A.CallTo(() => context.Arguments) + .Returns(new object[] { new MyCommand() }); + + A.CallTo(() => context.InterfaceMethod) + .Returns(methodInfo); + + var result = DomainObjectGrainFormatter.Format(context); + + Assert.Equal("ExecuteAsync(MyCommand)", result); + } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs b/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs index 95d2976ab..34818e231 100644 --- a/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectGrainTests.cs @@ -101,12 +101,9 @@ namespace Squidex.Infrastructure.Commands return Task.FromResult(null); } - public override void ApplyEvent(Envelope @event) + protected override MyDomainState OnEvent(Envelope @event) { - if (@event.Payload is ValueChanged valueChanged) - { - ApplySnapshot(new MyDomainState { Value = valueChanged.Value }); - } + return new MyDomainState { Value = ((ValueChanged)@event.Payload).Value }; } } diff --git a/tests/Squidex.Infrastructure.Tests/TestHelpers/MyGrain.cs b/tests/Squidex.Infrastructure.Tests/TestHelpers/MyGrain.cs index 6f9c0717c..3bec4b7c6 100644 --- a/tests/Squidex.Infrastructure.Tests/TestHelpers/MyGrain.cs +++ b/tests/Squidex.Infrastructure.Tests/TestHelpers/MyGrain.cs @@ -9,6 +9,7 @@ using System; using System.Threading.Tasks; using FakeItEasy; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.States; @@ -25,5 +26,10 @@ namespace Squidex.Infrastructure.TestHelpers { return Task.FromResult(null); } + + protected override MyDomainState OnEvent(Envelope @event) + { + return Snapshot; + } } }