mirror of https://github.com/Squidex/squidex.git
55 changed files with 973 additions and 543 deletions
@ -0,0 +1,29 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EventExtensions.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using Squidex.Infrastructure.CQRS; |
||||
|
|
||||
|
namespace Squidex.Events |
||||
|
{ |
||||
|
public static class EventExtensions |
||||
|
{ |
||||
|
public static Guid AppId(this EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return headers["AppId"].ToGuid(CultureInfo.InvariantCulture); |
||||
|
} |
||||
|
|
||||
|
public static Envelope<T> SetAppId<T>(this Envelope<T> envelope, Guid value) where T : class |
||||
|
{ |
||||
|
envelope.Headers.Set("AppId", value); |
||||
|
|
||||
|
return envelope; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,18 +1,17 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// AppEvent.cs
|
// FieldEvent.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
using System; |
|
||||
using Squidex.Infrastructure.CQRS.Events; |
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
namespace Squidex.Events |
namespace Squidex.Events.Schemas |
||||
{ |
{ |
||||
public class AppEvent : IEvent |
public abstract class FieldEvent : IEvent |
||||
{ |
{ |
||||
public Guid AppId { get; set; } |
public long FieldId { get; set; } |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AggregateHandler.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.CQRS.Commands |
||||
|
{ |
||||
|
public sealed class AggregateHandler : IAggregateHandler |
||||
|
{ |
||||
|
private readonly IDomainObjectRepository domainObjectRepository; |
||||
|
private readonly IDomainObjectFactory domainObjectFactory; |
||||
|
private readonly IEnumerable<IEventProcessor> eventProcessors; |
||||
|
|
||||
|
public IDomainObjectRepository Repository |
||||
|
{ |
||||
|
get { return domainObjectRepository; } |
||||
|
} |
||||
|
|
||||
|
public IDomainObjectFactory Factory |
||||
|
{ |
||||
|
get { return domainObjectFactory; } |
||||
|
} |
||||
|
|
||||
|
public AggregateHandler( |
||||
|
IDomainObjectFactory domainObjectFactory, |
||||
|
IDomainObjectRepository domainObjectRepository, |
||||
|
IEnumerable<IEventProcessor> eventProcessors) |
||||
|
{ |
||||
|
Guard.NotNull(eventProcessors, nameof(eventProcessors)); |
||||
|
Guard.NotNull(domainObjectFactory, nameof(domainObjectFactory)); |
||||
|
Guard.NotNull(domainObjectRepository, nameof(domainObjectRepository)); |
||||
|
|
||||
|
this.domainObjectFactory = domainObjectFactory; |
||||
|
this.domainObjectRepository = domainObjectRepository; |
||||
|
|
||||
|
this.eventProcessors = eventProcessors; |
||||
|
} |
||||
|
|
||||
|
public async Task CreateAsync<T>(IAggregateCommand command, Func<T, Task> creator) where T : class, IAggregate |
||||
|
{ |
||||
|
Guard.NotNull(creator, nameof(creator)); |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
var aggregate = domainObjectFactory.CreateNew<T>(command.AggregateId); |
||||
|
|
||||
|
await creator(aggregate); |
||||
|
|
||||
|
await Save(command, aggregate); |
||||
|
} |
||||
|
|
||||
|
public async Task UpdateAsync<T>(IAggregateCommand command, Func<T, Task> updater) where T : class, IAggregate |
||||
|
{ |
||||
|
Guard.NotNull(updater, nameof(updater)); |
||||
|
Guard.NotNull(command, nameof(command)); |
||||
|
|
||||
|
var aggregate = await domainObjectRepository.GetByIdAsync<T>(command.AggregateId); |
||||
|
|
||||
|
await updater(aggregate); |
||||
|
|
||||
|
await Save(command, aggregate); |
||||
|
} |
||||
|
|
||||
|
private async Task Save(ICommand command, IAggregate aggregate) |
||||
|
{ |
||||
|
var events = aggregate.GetUncomittedEvents(); |
||||
|
|
||||
|
foreach (var @event in events) |
||||
|
{ |
||||
|
foreach (var eventProcessor in eventProcessors) |
||||
|
{ |
||||
|
await eventProcessor.ProcessEventAsync(@event, aggregate, command); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
await domainObjectRepository.SaveAsync(aggregate, events, Guid.NewGuid()); |
||||
|
|
||||
|
aggregate.ClearUncommittedEvents(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,82 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// CommandHandler.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Commands |
|
||||
{ |
|
||||
public abstract class CommandHandler<T> : ICommandHandler where T : class, IAggregate |
|
||||
{ |
|
||||
private readonly IDomainObjectRepository domainObjectRepository; |
|
||||
private readonly IDomainObjectFactory domainObjectFactory; |
|
||||
|
|
||||
protected IDomainObjectRepository Repository |
|
||||
{ |
|
||||
get { return domainObjectRepository; } |
|
||||
} |
|
||||
|
|
||||
protected IDomainObjectFactory Factory |
|
||||
{ |
|
||||
get { return domainObjectFactory; } |
|
||||
} |
|
||||
|
|
||||
protected CommandHandler(IDomainObjectFactory domainObjectFactory, IDomainObjectRepository domainObjectRepository) |
|
||||
{ |
|
||||
Guard.NotNull(domainObjectFactory, nameof(domainObjectFactory)); |
|
||||
Guard.NotNull(domainObjectRepository, nameof(domainObjectRepository)); |
|
||||
|
|
||||
this.domainObjectFactory = domainObjectFactory; |
|
||||
this.domainObjectRepository = domainObjectRepository; |
|
||||
} |
|
||||
|
|
||||
protected async Task CreateAsync(IAggregateCommand command, Func<T, Task> creator) |
|
||||
{ |
|
||||
Guard.NotNull(creator, nameof(creator)); |
|
||||
Guard.NotNull(command, nameof(command)); |
|
||||
|
|
||||
var domainObject = domainObjectFactory.CreateNew<T>(command.AggregateId); |
|
||||
|
|
||||
await creator(domainObject); |
|
||||
|
|
||||
await domainObjectRepository.SaveAsync(domainObject, Guid.NewGuid()); |
|
||||
} |
|
||||
|
|
||||
protected Task CreateAsync(IAggregateCommand command, Action<T> creator) |
|
||||
{ |
|
||||
return CreateAsync(command, x => |
|
||||
{ |
|
||||
creator(x); |
|
||||
return Task.FromResult(true); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
protected async Task UpdateAsync(IAggregateCommand command, Func<T, Task> updater) |
|
||||
{ |
|
||||
Guard.NotNull(updater, nameof(updater)); |
|
||||
Guard.NotNull(command, nameof(command)); |
|
||||
|
|
||||
var domainObject = await domainObjectRepository.GetByIdAsync<T>(command.AggregateId); |
|
||||
|
|
||||
await updater(domainObject); |
|
||||
|
|
||||
await domainObjectRepository.SaveAsync(domainObject, Guid.NewGuid()); |
|
||||
} |
|
||||
|
|
||||
protected Task UpdateAsync(IAggregateCommand command, Action<T> updater) |
|
||||
{ |
|
||||
return UpdateAsync(command, x => |
|
||||
{ |
|
||||
updater(x); |
|
||||
return Task.FromResult(true); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
public abstract Task<bool> HandleAsync(CommandContext context); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,20 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IAggregateHandler.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.CQRS.Commands |
||||
|
{ |
||||
|
public interface IAggregateHandler |
||||
|
{ |
||||
|
Task CreateAsync<T>(IAggregateCommand command, Func<T, Task> creator) where T : class, IAggregate; |
||||
|
|
||||
|
Task UpdateAsync<T>(IAggregateCommand command, Func<T, Task> updater) where T : class, IAggregate; |
||||
|
} |
||||
|
} |
||||
@ -1,37 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// EnvelopeFactory.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using NodaTime; |
|
||||
using Squidex.Infrastructure.CQRS.Events; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS |
|
||||
{ |
|
||||
public static class EnvelopeFactory |
|
||||
{ |
|
||||
public static Envelope<IEvent> ForEvent(IEvent @event, IAggregate aggregate) |
|
||||
{ |
|
||||
var eventId = Guid.NewGuid(); |
|
||||
|
|
||||
var envelope = |
|
||||
new Envelope<IEvent>(@event) |
|
||||
.SetAggregateId(aggregate.Id) |
|
||||
.SetEventId(eventId) |
|
||||
.SetTimestamp(SystemClock.Instance.GetCurrentInstant()); |
|
||||
|
|
||||
var appAggregate = aggregate as IAppAggregate; |
|
||||
|
|
||||
if (appAggregate != null) |
|
||||
{ |
|
||||
envelope = envelope.SetAppId(appAggregate.AppId); |
|
||||
} |
|
||||
|
|
||||
return envelope; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,29 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EnrichWithAggregateIdProcessor.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Infrastructure.CQRS.Commands; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.CQRS.Events |
||||
|
{ |
||||
|
public sealed class EnrichWithAggregateIdProcessor : IEventProcessor |
||||
|
{ |
||||
|
public Task ProcessEventAsync(Envelope<IEvent> @event, IAggregate aggregate, ICommand command) |
||||
|
{ |
||||
|
var aggregateCommand = command as IAggregateCommand; |
||||
|
|
||||
|
if (aggregateCommand != null) |
||||
|
{ |
||||
|
@event.SetAggregateId(aggregateCommand.AggregateId); |
||||
|
} |
||||
|
|
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EnrichWithUserProcessor.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Infrastructure.CQRS.Commands; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.CQRS.Events |
||||
|
{ |
||||
|
public sealed class EnrichWithUserProcessor : IEventProcessor |
||||
|
{ |
||||
|
public Task ProcessEventAsync(Envelope<IEvent> @event, IAggregate aggregate, ICommand command) |
||||
|
{ |
||||
|
var userCommand = command as IUserCommand; |
||||
|
|
||||
|
if (userCommand != null) |
||||
|
{ |
||||
|
@event.SetUser(userCommand.User); |
||||
|
} |
||||
|
|
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IEventProcessor.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Infrastructure.CQRS.Commands; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.CQRS.Events |
||||
|
{ |
||||
|
public interface IEventProcessor |
||||
|
{ |
||||
|
Task ProcessEventAsync(Envelope<IEvent> @event, IAggregate aggregate, ICommand command); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EnrichWithAppIdProcessor.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Events; |
||||
|
using Squidex.Infrastructure.CQRS; |
||||
|
using Squidex.Infrastructure.CQRS.Commands; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
|
||||
|
namespace Squidex.Write |
||||
|
{ |
||||
|
public sealed class EnrichWithAppIdProcessor : IEventProcessor |
||||
|
{ |
||||
|
public Task ProcessEventAsync(Envelope<IEvent> @event, IAggregate aggregate, ICommand command) |
||||
|
{ |
||||
|
var appCommand = command as IAppCommand; |
||||
|
|
||||
|
if (appCommand != null) |
||||
|
{ |
||||
|
@event.SetAppId(appCommand.AppId); |
||||
|
} |
||||
|
|
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,17 +1,18 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// IAppAggregate.cs
|
// SquidexCommand.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
using System; |
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS.Commands; |
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS |
namespace Squidex.Write |
||||
{ |
{ |
||||
public interface IAppAggregate : IAggregate |
public abstract class SquidexCommand : AggregateCommand, IUserCommand |
||||
{ |
{ |
||||
Guid AppId { get; } |
public UserToken User { get; set; } |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,209 @@ |
|||||
|
// ==========================================================================
|
||||
|
// AggregateHandlerTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Moq; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.CQRS.Commands |
||||
|
{ |
||||
|
public class AggregateHandlerTests |
||||
|
{ |
||||
|
private sealed class MyEvent : IEvent |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
private sealed class MyCommand : IAggregateCommand |
||||
|
{ |
||||
|
public Guid AggregateId { get; set; } |
||||
|
} |
||||
|
|
||||
|
private sealed class MyDomainObject : DomainObject |
||||
|
{ |
||||
|
public MyDomainObject(Guid id, int version) |
||||
|
: base(id, version) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public MyDomainObject RaiseNewEvent(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
RaiseEvent(@event); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
protected override void DispatchEvent(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private readonly Mock<IDomainObjectFactory> factory = new Mock<IDomainObjectFactory>(); |
||||
|
private readonly Mock<IDomainObjectRepository> repository = new Mock<IDomainObjectRepository>(); |
||||
|
private readonly Mock<IEventProcessor> processor1 = new Mock<IEventProcessor>(); |
||||
|
private readonly Mock<IEventProcessor> processor2 = new Mock<IEventProcessor>(); |
||||
|
private readonly Envelope<IEvent> event1 = new Envelope<IEvent>(new MyEvent()); |
||||
|
private readonly Envelope<IEvent> event2 = new Envelope<IEvent>(new MyEvent()); |
||||
|
private readonly MyCommand command; |
||||
|
private readonly AggregateHandler sut; |
||||
|
private readonly MyDomainObject domainObject; |
||||
|
|
||||
|
public AggregateHandlerTests() |
||||
|
{ |
||||
|
var processors = new[] { processor1.Object, processor2.Object }; |
||||
|
|
||||
|
sut = new AggregateHandler(factory.Object, repository.Object, processors); |
||||
|
|
||||
|
domainObject = |
||||
|
new MyDomainObject(Guid.NewGuid(), 1) |
||||
|
.RaiseNewEvent(event1) |
||||
|
.RaiseNewEvent(event2); |
||||
|
|
||||
|
command = new MyCommand { AggregateId = domainObject.Id }; |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_provide_access_to_factory() |
||||
|
{ |
||||
|
Assert.Equal(factory.Object, sut.Factory); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_provide_access_to_repository() |
||||
|
{ |
||||
|
Assert.Equal(repository.Object, sut.Repository); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Create_async_should_create_domain_object_and_save() |
||||
|
{ |
||||
|
factory.Setup(x => x.CreateNew(typeof(MyDomainObject), domainObject.Id)) |
||||
|
.Returns(domainObject) |
||||
|
.Verifiable(); |
||||
|
|
||||
|
await TestFlowAsync(async () => |
||||
|
{ |
||||
|
MyDomainObject passedDomainObject = null; |
||||
|
|
||||
|
await sut.CreateAsync<MyDomainObject>(command, x => |
||||
|
{ |
||||
|
passedDomainObject = x; |
||||
|
|
||||
|
return TaskHelper.Done; |
||||
|
}); |
||||
|
|
||||
|
Assert.Equal(domainObject, passedDomainObject); |
||||
|
}); |
||||
|
|
||||
|
factory.VerifyAll(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Create_sync_should_create_domain_object_and_save() |
||||
|
{ |
||||
|
factory.Setup(x => x.CreateNew(typeof(MyDomainObject), domainObject.Id)) |
||||
|
.Returns(domainObject) |
||||
|
.Verifiable(); |
||||
|
|
||||
|
await TestFlowAsync(async () => |
||||
|
{ |
||||
|
MyDomainObject passedDomainObject = null; |
||||
|
|
||||
|
await sut.CreateAsync<MyDomainObject>(command, x => |
||||
|
{ |
||||
|
passedDomainObject = x; |
||||
|
}); |
||||
|
|
||||
|
Assert.Equal(domainObject, passedDomainObject); |
||||
|
}); |
||||
|
|
||||
|
factory.VerifyAll(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Update_async_should_create_domain_object_and_save() |
||||
|
{ |
||||
|
repository.Setup(x => x.GetByIdAsync<MyDomainObject>(command.AggregateId, int.MaxValue)) |
||||
|
.Returns(Task.FromResult(domainObject)) |
||||
|
.Verifiable(); |
||||
|
|
||||
|
await TestFlowAsync(async () => |
||||
|
{ |
||||
|
MyDomainObject passedDomainObject = null; |
||||
|
|
||||
|
await sut.UpdateAsync<MyDomainObject>(command, x => |
||||
|
{ |
||||
|
passedDomainObject = x; |
||||
|
|
||||
|
return TaskHelper.Done; |
||||
|
}); |
||||
|
|
||||
|
Assert.Equal(domainObject, passedDomainObject); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Update_sync_should_create_domain_object_and_save() |
||||
|
{ |
||||
|
repository.Setup(x => x.GetByIdAsync<MyDomainObject>(command.AggregateId, int.MaxValue)) |
||||
|
.Returns(Task.FromResult(domainObject)) |
||||
|
.Verifiable(); |
||||
|
|
||||
|
await TestFlowAsync(async () => |
||||
|
{ |
||||
|
MyDomainObject passedDomainObject = null; |
||||
|
|
||||
|
await sut.UpdateAsync<MyDomainObject>(command, x => |
||||
|
{ |
||||
|
passedDomainObject = x; |
||||
|
}); |
||||
|
|
||||
|
Assert.Equal(domainObject, passedDomainObject); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private async Task TestFlowAsync(Func<Task> action) |
||||
|
{ |
||||
|
repository.Setup(x => x.SaveAsync(domainObject, |
||||
|
It.IsAny<ICollection<Envelope<IEvent>>>(), |
||||
|
It.IsAny<Guid>())) |
||||
|
.Returns(TaskHelper.Done) |
||||
|
.Verifiable(); |
||||
|
|
||||
|
processor1.Setup(x => x.ProcessEventAsync( |
||||
|
It.Is<Envelope<IEvent>>(y => y.Payload == event1.Payload), domainObject, command)) |
||||
|
.Returns(TaskHelper.Done) |
||||
|
.Verifiable(); |
||||
|
|
||||
|
processor2.Setup(x => x.ProcessEventAsync( |
||||
|
It.Is<Envelope<IEvent>>(y => y.Payload == event1.Payload), domainObject, command)) |
||||
|
.Returns(TaskHelper.Done) |
||||
|
.Verifiable(); |
||||
|
|
||||
|
processor1.Setup(x => x.ProcessEventAsync( |
||||
|
It.Is<Envelope<IEvent>>(y => y.Payload == event2.Payload), domainObject, command)) |
||||
|
.Returns(TaskHelper.Done) |
||||
|
.Verifiable(); |
||||
|
|
||||
|
processor2.Setup(x => x.ProcessEventAsync( |
||||
|
It.Is<Envelope<IEvent>>(y => y.Payload == event2.Payload), domainObject, command)) |
||||
|
.Returns(TaskHelper.Done) |
||||
|
.Verifiable(); |
||||
|
|
||||
|
await action(); |
||||
|
|
||||
|
processor1.VerifyAll(); |
||||
|
processor2.VerifyAll(); |
||||
|
|
||||
|
repository.VerifyAll(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,129 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// CommandsHandlerTests.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using Moq; |
|
||||
using Squidex.Infrastructure.CQRS.Events; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Commands |
|
||||
{ |
|
||||
public class CommandsHandlerTests |
|
||||
{ |
|
||||
private readonly Mock<IDomainObjectFactory> domainObjectFactory = new Mock<IDomainObjectFactory>(); |
|
||||
private readonly Mock<IDomainObjectRepository> domainObjectRepository = new Mock<IDomainObjectRepository>(); |
|
||||
private readonly TestCommandHandler sut; |
|
||||
private readonly Guid id = Guid.NewGuid(); |
|
||||
|
|
||||
private sealed class TestCommand : AggregateCommand |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
private sealed class TestDomainObject : DomainObject |
|
||||
{ |
|
||||
public TestDomainObject(Guid id, int version) : base(id, version) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected override void DispatchEvent(Envelope<IEvent> @event) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private sealed class TestCommandHandler : CommandHandler<TestDomainObject> |
|
||||
{ |
|
||||
public TestCommandHandler(IDomainObjectFactory domainObjectFactory, IDomainObjectRepository domainObjectRepository) |
|
||||
: base(domainObjectFactory, domainObjectRepository) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public override Task<bool> HandleAsync(CommandContext context) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public IDomainObjectFactory TestFactory |
|
||||
{ |
|
||||
get { return Factory; } |
|
||||
} |
|
||||
|
|
||||
public IDomainObjectRepository TestRepository |
|
||||
{ |
|
||||
get { return Repository; } |
|
||||
} |
|
||||
|
|
||||
public Task CreateTestAsync(IAggregateCommand command, Action<TestDomainObject> creator) |
|
||||
{ |
|
||||
return CreateAsync(command, creator); |
|
||||
} |
|
||||
|
|
||||
public Task UpdateTestAsync(IAggregateCommand command, Action<TestDomainObject> updater) |
|
||||
{ |
|
||||
return UpdateAsync(command, updater); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public CommandsHandlerTests() |
|
||||
{ |
|
||||
sut = new TestCommandHandler(domainObjectFactory.Object, domainObjectRepository.Object); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_provide_factory() |
|
||||
{ |
|
||||
Assert.Equal(domainObjectFactory.Object, sut.TestFactory); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_provide_repository() |
|
||||
{ |
|
||||
Assert.Equal(domainObjectRepository.Object, sut.TestRepository); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_retrieve_from_repository_and_update() |
|
||||
{ |
|
||||
var command = new TestCommand { AggregateId = id }; |
|
||||
|
|
||||
var domainObject = new TestDomainObject(id, 123); |
|
||||
|
|
||||
domainObjectRepository.Setup(x => x.GetByIdAsync<TestDomainObject>(id, int.MaxValue)).Returns(Task.FromResult(domainObject)).Verifiable(); |
|
||||
domainObjectRepository.Setup(x => x.SaveAsync(domainObject, It.IsAny<Guid>())).Returns(Task.FromResult(true)).Verifiable(); |
|
||||
|
|
||||
var isCalled = false; |
|
||||
|
|
||||
await sut.UpdateTestAsync(command, x => isCalled = true); |
|
||||
|
|
||||
domainObjectRepository.VerifyAll(); |
|
||||
|
|
||||
Assert.True(isCalled); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_create_with_factory_and_update() |
|
||||
{ |
|
||||
var command = new TestCommand { AggregateId = id }; |
|
||||
|
|
||||
var domainObject = new TestDomainObject(id, 123); |
|
||||
|
|
||||
domainObjectFactory.Setup(x => x.CreateNew(typeof(TestDomainObject), id)).Returns(domainObject).Verifiable(); |
|
||||
domainObjectRepository.Setup(x => x.SaveAsync(domainObject, It.IsAny<Guid>())).Returns(Task.FromResult(true)).Verifiable(); |
|
||||
|
|
||||
var isCalled = false; |
|
||||
|
|
||||
await sut.CreateTestAsync(command, x => isCalled = true); |
|
||||
|
|
||||
domainObjectFactory.VerifyAll(); |
|
||||
domainObjectRepository.VerifyAll(); |
|
||||
|
|
||||
Assert.True(isCalled); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,56 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EnrichWithAggregateIdProcessorTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Infrastructure.CQRS.Commands; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.CQRS.Events |
||||
|
{ |
||||
|
public class EnrichWithAggregateIdProcessorTests |
||||
|
{ |
||||
|
public sealed class MyAggregateIdCommand : IAggregateCommand |
||||
|
{ |
||||
|
public Guid AggregateId { get; set; } |
||||
|
} |
||||
|
|
||||
|
public sealed class MyNormalCommand : ICommand |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public sealed class MyEvent : IEvent |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
private readonly EnrichWithAggregateIdProcessor sut = new EnrichWithAggregateIdProcessor(); |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_do_anything_if_not_aggregate_command() |
||||
|
{ |
||||
|
var envelope = new Envelope<IEvent>(new MyEvent()); |
||||
|
|
||||
|
await sut.ProcessEventAsync(envelope, null, new MyNormalCommand()); |
||||
|
|
||||
|
Assert.False(envelope.Headers.Contains("AggregateId")); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_attach_aggregate_to_event_envelope() |
||||
|
{ |
||||
|
var aggregateId = Guid.NewGuid(); |
||||
|
var aggregateCommand = new MyAggregateIdCommand { AggregateId = aggregateId }; |
||||
|
|
||||
|
var envelope = new Envelope<IEvent>(new MyEvent()); |
||||
|
|
||||
|
await sut.ProcessEventAsync(envelope, null, aggregateCommand); |
||||
|
|
||||
|
Assert.Equal(aggregateId, envelope.Headers.AggregateId()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EnrichWithUserProcessorTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Infrastructure.CQRS.Commands; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.CQRS.Events |
||||
|
{ |
||||
|
public class EnrichWithUserProcessorTests |
||||
|
{ |
||||
|
public sealed class MyUserCommand : IUserCommand |
||||
|
{ |
||||
|
public UserToken User { get; set; } |
||||
|
} |
||||
|
|
||||
|
public sealed class MyNormalCommand : ICommand |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public sealed class MyEvent : IEvent |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
private readonly EnrichWithUserProcessor sut = new EnrichWithUserProcessor(); |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_do_anything_if_not_user_command() |
||||
|
{ |
||||
|
var envelope = new Envelope<IEvent>(new MyEvent()); |
||||
|
|
||||
|
await sut.ProcessEventAsync(envelope, null, new MyNormalCommand()); |
||||
|
|
||||
|
Assert.False(envelope.Headers.Contains("User")); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_attach_user_to_event_envelope() |
||||
|
{ |
||||
|
var user = new UserToken("subject", "123"); |
||||
|
var userCommand = new MyUserCommand { User = user }; |
||||
|
|
||||
|
var envelope = new Envelope<IEvent>(new MyEvent()); |
||||
|
|
||||
|
await sut.ProcessEventAsync(envelope, null, userCommand); |
||||
|
|
||||
|
Assert.Equal(user, envelope.Headers.User()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EnrichWithAppIdProcessorTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Events; |
||||
|
using Squidex.Infrastructure.CQRS; |
||||
|
using Squidex.Infrastructure.CQRS.Commands; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Write |
||||
|
{ |
||||
|
public class EnrichWithAppIdProcessorTests |
||||
|
{ |
||||
|
public sealed class MyAppCommand : AppAggregateCommand |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public sealed class MyNormalCommand : ICommand |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public sealed class MyEvent : IEvent |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
private readonly EnrichWithAppIdProcessor sut = new EnrichWithAppIdProcessor(); |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_do_anything_if_not_app_command() |
||||
|
{ |
||||
|
var envelope = new Envelope<IEvent>(new MyEvent()); |
||||
|
|
||||
|
await sut.ProcessEventAsync(envelope, null, new MyNormalCommand()); |
||||
|
|
||||
|
Assert.False(envelope.Headers.Contains("AppId")); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_attach_app_id_to_event_envelope() |
||||
|
{ |
||||
|
var appId = Guid.NewGuid(); |
||||
|
var appCommand = new MyAppCommand { AggregateId = appId }; |
||||
|
|
||||
|
var envelope = new Envelope<IEvent>(new MyEvent()); |
||||
|
|
||||
|
await sut.ProcessEventAsync(envelope, null, appCommand); |
||||
|
|
||||
|
Assert.Equal(appId, envelope.Headers.AppId()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue