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
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
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
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// 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