Browse Source

Event flow improved

pull/1/head
Sebastian 9 years ago
parent
commit
3708ed76c7
  1. 29
      src/Squidex.Events/EventExtensions.cs
  2. 5
      src/Squidex.Events/Schemas/FieldAdded.cs
  3. 4
      src/Squidex.Events/Schemas/FieldDeleted.cs
  4. 4
      src/Squidex.Events/Schemas/FieldDisabled.cs
  5. 4
      src/Squidex.Events/Schemas/FieldEnabled.cs
  6. 9
      src/Squidex.Events/Schemas/FieldEvent.cs
  7. 4
      src/Squidex.Events/Schemas/FieldHidden.cs
  8. 4
      src/Squidex.Events/Schemas/FieldShown.cs
  9. 5
      src/Squidex.Events/Schemas/FieldUpdated.cs
  10. 3
      src/Squidex.Events/Schemas/SchemaCreated.cs
  11. 88
      src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs
  12. 82
      src/Squidex.Infrastructure/CQRS/Commands/CommandHandler.cs
  13. 22
      src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs
  14. 20
      src/Squidex.Infrastructure/CQRS/Commands/IAggregateHandler.cs
  15. 4
      src/Squidex.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs
  16. 10
      src/Squidex.Infrastructure/CQRS/CommonHeaders.cs
  17. 14
      src/Squidex.Infrastructure/CQRS/DomainObject.cs
  18. 38
      src/Squidex.Infrastructure/CQRS/Envelope.cs
  19. 8
      src/Squidex.Infrastructure/CQRS/EnvelopeExtensions.cs
  20. 37
      src/Squidex.Infrastructure/CQRS/EnvelopeFactory.cs
  21. 11
      src/Squidex.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs
  22. 29
      src/Squidex.Infrastructure/CQRS/Events/EnrichWithAggregateIdProcessor.cs
  23. 29
      src/Squidex.Infrastructure/CQRS/Events/EnrichWithUserProcessor.cs
  24. 18
      src/Squidex.Infrastructure/CQRS/Events/IEventProcessor.cs
  25. 1
      src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs
  26. 1
      src/Squidex.Store.MongoDb/Utils/EntityMapper.cs
  27. 3
      src/Squidex.Write/AppAggregateCommand.cs
  28. 3
      src/Squidex.Write/AppCommand.cs
  29. 57
      src/Squidex.Write/Apps/AppCommandHandler.cs
  30. 32
      src/Squidex.Write/EnrichWithAppIdProcessor.cs
  31. 15
      src/Squidex.Write/Schemas/Commands/CreateSchema.cs
  32. 46
      src/Squidex.Write/Schemas/SchemaCommandHandler.cs
  33. 10
      src/Squidex.Write/Schemas/SchemaDomainObject.cs
  34. 11
      src/Squidex.Write/SquidexCommand.cs
  35. 14
      src/Squidex/Config/Domain/WriteModule.cs
  36. 209
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/AggregateHandlerTests.cs
  37. 4
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/CommandContextTests.cs
  38. 129
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/CommandsHandlerTests.cs
  39. 10
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampHandlerTests.cs
  40. 4
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/InMemoryCommandBusTests.cs
  41. 10
      tests/Squidex.Infrastructure.Tests/CQRS/DomainObjectTest.cs
  42. 10
      tests/Squidex.Infrastructure.Tests/CQRS/EnvelopeExtensionsTests.cs
  43. 20
      tests/Squidex.Infrastructure.Tests/CQRS/EventStore/DefaultNameResolverTests.cs
  44. 13
      tests/Squidex.Infrastructure.Tests/CQRS/EventStore/EventStoreFormatterTests.cs
  45. 56
      tests/Squidex.Infrastructure.Tests/CQRS/Events/EnrichWithAggregateIdProcessorTests.cs
  46. 55
      tests/Squidex.Infrastructure.Tests/CQRS/Events/EnrichWithUserProcessorTests.cs
  47. 144
      tests/Squidex.Infrastructure.Tests/DispatchingTests.cs
  48. 8
      tests/Squidex.Infrastructure.Tests/GuardTests.cs
  49. 12
      tests/Squidex.Infrastructure.Tests/Reflection/ReflectionExtensionTests.cs
  50. 22
      tests/Squidex.Infrastructure.Tests/Reflection/SimpleMapperTests.cs
  51. 7
      tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs
  52. 58
      tests/Squidex.Write.Tests/EnrichWithAppIdProcessorTests.cs
  53. 5
      tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs
  54. 2
      tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs
  55. 62
      tests/Squidex.Write.Tests/Utils/HandlerTestBase.cs

29
src/Squidex.Events/EventExtensions.cs

@ -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;
}
}
}

5
src/Squidex.Events/Schemas/FieldAdded.cs

@ -8,15 +8,12 @@
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Events.Schemas namespace Squidex.Events.Schemas
{ {
[TypeName("FieldAddedEvent")] [TypeName("FieldAddedEvent")]
public class FieldAdded : IEvent public class FieldAdded : FieldEvent
{ {
public long FieldId { get; set; }
public string Name { get; set; } public string Name { get; set; }
public FieldProperties Properties { get; set; } public FieldProperties Properties { get; set; }

4
src/Squidex.Events/Schemas/FieldDeleted.cs

@ -7,13 +7,11 @@
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Events.Schemas namespace Squidex.Events.Schemas
{ {
[TypeName("FieldDeletedEvent")] [TypeName("FieldDeletedEvent")]
public class FieldDeleted : IEvent public class FieldDeleted : FieldEvent
{ {
public long FieldId { get; set; }
} }
} }

4
src/Squidex.Events/Schemas/FieldDisabled.cs

@ -7,13 +7,11 @@
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Events.Schemas namespace Squidex.Events.Schemas
{ {
[TypeName("FieldDisabledEvent")] [TypeName("FieldDisabledEvent")]
public class FieldDisabled : IEvent public class FieldDisabled : FieldEvent
{ {
public long FieldId { get; set; }
} }
} }

4
src/Squidex.Events/Schemas/FieldEnabled.cs

@ -7,13 +7,11 @@
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Events.Schemas namespace Squidex.Events.Schemas
{ {
[TypeName("FieldEnabledEvent")] [TypeName("FieldEnabledEvent")]
public class FieldEnabled : IEvent public class FieldEnabled : FieldEvent
{ {
public long FieldId { get; set; }
} }
} }

9
src/Squidex.Events/AppEvent.cs → src/Squidex.Events/Schemas/FieldEvent.cs

@ -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; }
} }
} }

4
src/Squidex.Events/Schemas/FieldHidden.cs

@ -7,13 +7,11 @@
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Events.Schemas namespace Squidex.Events.Schemas
{ {
[TypeName("FieldHiddenEvent")] [TypeName("FieldHiddenEvent")]
public class FieldHidden : IEvent public class FieldHidden : FieldEvent
{ {
public long FieldId { get; set; }
} }
} }

4
src/Squidex.Events/Schemas/FieldShown.cs

@ -7,13 +7,11 @@
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Events.Schemas namespace Squidex.Events.Schemas
{ {
[TypeName("FieldShownEvent")] [TypeName("FieldShownEvent")]
public class FieldShown : IEvent public class FieldShown : FieldEvent
{ {
public long FieldId { get; set; }
} }
} }

5
src/Squidex.Events/Schemas/FieldUpdated.cs

@ -8,15 +8,12 @@
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Events.Schemas namespace Squidex.Events.Schemas
{ {
[TypeName("FieldUpdatedEvent")] [TypeName("FieldUpdatedEvent")]
public class FieldUpdated : IEvent public class FieldUpdated : FieldEvent
{ {
public long FieldId { get; set; }
public FieldProperties Properties { get; set; } public FieldProperties Properties { get; set; }
} }
} }

3
src/Squidex.Events/Schemas/SchemaCreated.cs

@ -8,11 +8,12 @@
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Events.Schemas namespace Squidex.Events.Schemas
{ {
[TypeName("SchemaCreatedEvent")] [TypeName("SchemaCreatedEvent")]
public class SchemaCreated : AppEvent public class SchemaCreated : IEvent
{ {
public string Name { get; set; } public string Name { get; set; }

88
src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs

@ -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();
}
}
}

82
src/Squidex.Infrastructure/CQRS/Commands/CommandHandler.cs

@ -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);
}
}

22
src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs

@ -7,6 +7,8 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.CQRS.Commands namespace Squidex.Infrastructure.CQRS.Commands
{ {
@ -16,5 +18,25 @@ namespace Squidex.Infrastructure.CQRS.Commands
{ {
return (T)factory.CreateNew(typeof(T), id); return (T)factory.CreateNew(typeof(T), id);
} }
public static Task CreateAsync<T>(this IAggregateHandler handler, IAggregateCommand command, Action<T> creator) where T : class, IAggregate
{
return handler.CreateAsync<T>(command, x =>
{
creator(x);
return TaskHelper.Done;
});
}
public static Task UpdateAsync<T>(this IAggregateHandler handler, IAggregateCommand command, Action<T> creator) where T : class, IAggregate
{
return handler.UpdateAsync<T>(command, x =>
{
creator(x);
return TaskHelper.Done;
});
}
} }
} }

20
src/Squidex.Infrastructure/CQRS/Commands/IAggregateHandler.cs

@ -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;
}
}

4
src/Squidex.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs

@ -7,7 +7,9 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Infrastructure.CQRS.Commands namespace Squidex.Infrastructure.CQRS.Commands
{ {
@ -15,6 +17,6 @@ namespace Squidex.Infrastructure.CQRS.Commands
{ {
Task<TDomainObject> GetByIdAsync<TDomainObject>(Guid id, int version = int.MaxValue) where TDomainObject : class, IAggregate; Task<TDomainObject> GetByIdAsync<TDomainObject>(Guid id, int version = int.MaxValue) where TDomainObject : class, IAggregate;
Task SaveAsync(IAggregate domainObject, Guid commitId); Task SaveAsync(IAggregate domainObject, ICollection<Envelope<IEvent>> events, Guid commitId);
} }
} }

10
src/Squidex.Infrastructure/CQRS/CommonHeaders.cs

@ -5,15 +5,21 @@
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Infrastructure.CQRS namespace Squidex.Infrastructure.CQRS
{ {
public sealed class CommonHeaders public sealed class CommonHeaders
{ {
public const string AggregateId = "AggregateId"; public const string AggregateId = "AggregateId";
public const string CommitId = "CommitId"; public const string CommitId = "CommitId";
public const string Timestamp = "Timestamp";
public const string AppId = "AppId";
public const string EventId = "EventId"; public const string EventId = "EventId";
public const string EventNumber = "EventNumber"; public const string EventNumber = "EventNumber";
public const string Timestamp = "Timestamp";
public const string User = "User";
} }
} }

14
src/Squidex.Infrastructure/CQRS/DomainObject.cs

@ -40,25 +40,23 @@ namespace Squidex.Infrastructure.CQRS
protected abstract void DispatchEvent(Envelope<IEvent> @event); protected abstract void DispatchEvent(Envelope<IEvent> @event);
protected void RaiseEvent(IEvent @event) private void ApplyEventCore(Envelope<IEvent> @event)
{ {
RaiseEvent(EnvelopeFactory.ForEvent(@event, this)); DispatchEvent(@event); version++;
} }
private void ApplyEventCore(Envelope<IEvent> @event) protected void RaiseEvent(IEvent @event)
{ {
DispatchEvent(@event); version++; RaiseEvent(Envelope<IEvent>.Create(@event));
} }
protected void RaiseEvent<TEvent>(Envelope<TEvent> @event) where TEvent : class, IEvent protected void RaiseEvent<TEvent>(Envelope<TEvent> @event) where TEvent : class, IEvent
{ {
Guard.NotNull(@event, nameof(@event)); Guard.NotNull(@event, nameof(@event));
var envelopeToAdd = @event.To<IEvent>(); uncomittedEvents.Add(@event.To<IEvent>());
uncomittedEvents.Add(envelopeToAdd);
ApplyEventCore(envelopeToAdd); ApplyEventCore(@event.To<IEvent>());
} }
void IAggregate.ApplyEvent(Envelope<IEvent> @event) void IAggregate.ApplyEvent(Envelope<IEvent> @event)

38
src/Squidex.Infrastructure/CQRS/Envelope.cs

@ -5,6 +5,10 @@
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using NodaTime;
namespace Squidex.Infrastructure.CQRS namespace Squidex.Infrastructure.CQRS
{ {
public class Envelope<TPayload> where TPayload : class public class Envelope<TPayload> where TPayload : class
@ -14,18 +18,22 @@ namespace Squidex.Infrastructure.CQRS
public EnvelopeHeaders Headers public EnvelopeHeaders Headers
{ {
get get { return headers; }
{
return headers;
}
} }
public TPayload Payload public TPayload Payload
{ {
get get { return payload; }
{ }
return payload;
} public Envelope(TPayload payload)
: this(payload, new EnvelopeHeaders())
{
}
public Envelope(TPayload payload, PropertiesBag bag)
: this(payload, new EnvelopeHeaders(bag))
{
} }
public Envelope(TPayload payload, EnvelopeHeaders headers) public Envelope(TPayload payload, EnvelopeHeaders headers)
@ -37,14 +45,16 @@ namespace Squidex.Infrastructure.CQRS
this.headers = headers; this.headers = headers;
} }
public Envelope(TPayload payload) public static Envelope<TPayload> Create(TPayload payload)
: this(payload, new EnvelopeHeaders())
{ {
} var eventId = Guid.NewGuid();
public Envelope(TPayload payload, PropertiesBag bag) var envelope =
: this(payload, new EnvelopeHeaders(bag)) new Envelope<TPayload>(payload)
{ .SetEventId(eventId)
.SetTimestamp(SystemClock.Instance.GetCurrentInstant());
return envelope;
} }
public Envelope<TOther> To<TOther>() where TOther : class public Envelope<TOther> To<TOther>() where TOther : class

8
src/Squidex.Infrastructure/CQRS/EnvelopeExtensions.cs

@ -62,14 +62,14 @@ namespace Squidex.Infrastructure.CQRS
return envelope; return envelope;
} }
public static Guid AppId(this EnvelopeHeaders headers) public static UserToken User(this EnvelopeHeaders headers)
{ {
return headers[CommonHeaders.AppId].ToGuid(CultureInfo.InvariantCulture); return UserToken.Parse(headers[CommonHeaders.User].ToString());
} }
public static Envelope<T> SetAppId<T>(this Envelope<T> envelope, Guid value) where T : class public static Envelope<T> SetUser<T>(this Envelope<T> envelope, UserToken value) where T : class
{ {
envelope.Headers.Set(CommonHeaders.AppId, value); envelope.Headers.Set(CommonHeaders.User, value.ToString());
return envelope; return envelope;
} }

37
src/Squidex.Infrastructure/CQRS/EnvelopeFactory.cs

@ -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;
}
}
}

11
src/Squidex.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs

@ -14,6 +14,7 @@ using System.Threading.Tasks;
using EventStore.ClientAPI; using EventStore.ClientAPI;
using EventStore.ClientAPI.SystemData; using EventStore.ClientAPI.SystemData;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.CQRS.Events;
// ReSharper disable RedundantAssignment // ReSharper disable RedundantAssignment
// ReSharper disable ConvertIfStatementToSwitchStatement // ReSharper disable ConvertIfStatementToSwitchStatement
@ -98,19 +99,17 @@ namespace Squidex.Infrastructure.CQRS.EventStore
return domainObject; return domainObject;
} }
public async Task SaveAsync(IAggregate domainObject, Guid commitId) public async Task SaveAsync(IAggregate domainObject, ICollection<Envelope<IEvent>> events, Guid commitId)
{ {
Guard.NotNull(domainObject, nameof(domainObject)); Guard.NotNull(domainObject, nameof(domainObject));
var streamName = nameResolver.GetStreamName(domainObject.GetType(), domainObject.Id); var streamName = nameResolver.GetStreamName(domainObject.GetType(), domainObject.Id);
var newEvents = domainObject.GetUncomittedEvents();
var versionCurrent = domainObject.Version; var versionCurrent = domainObject.Version;
var versionPrevious = versionCurrent - newEvents.Count; var versionBefore = versionCurrent - events.Count;
var versionExpected = versionPrevious == 0 ? ExpectedVersion.NoStream : versionPrevious - 1; var versionExpected = versionBefore == 0 ? ExpectedVersion.NoStream : versionBefore - 1;
var eventsToSave = newEvents.Select(x => formatter.ToEventData(x, commitId)).ToList(); var eventsToSave = events.Select(x => formatter.ToEventData(x, commitId)).ToList();
await InsertEventsAsync(streamName, versionExpected, eventsToSave); await InsertEventsAsync(streamName, versionExpected, eventsToSave);

29
src/Squidex.Infrastructure/CQRS/Events/EnrichWithAggregateIdProcessor.cs

@ -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;
}
}
}

29
src/Squidex.Infrastructure/CQRS/Events/EnrichWithUserProcessor.cs

@ -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;
}
}
}

18
src/Squidex.Infrastructure/CQRS/Events/IEventProcessor.cs

@ -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);
}
}

1
src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs

@ -15,6 +15,7 @@ using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.Schemas.Repositories; using Squidex.Read.Schemas.Repositories;
using Squidex.Read.Utils; using Squidex.Read.Utils;
using Squidex.Events;
// ReSharper disable InvertIf // ReSharper disable InvertIf

1
src/Squidex.Store.MongoDb/Utils/EntityMapper.cs

@ -11,6 +11,7 @@ using System.Threading.Tasks;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Events;
using Squidex.Infrastructure.CQRS; using Squidex.Infrastructure.CQRS;
using Squidex.Read; using Squidex.Read;

3
src/Squidex.Write/AppAggregateCommand.cs

@ -7,11 +7,10 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Squidex.Infrastructure.CQRS.Commands;
namespace Squidex.Write namespace Squidex.Write
{ {
public class AppAggregateCommand : AggregateCommand, IAppCommand public class AppAggregateCommand : SquidexCommand, IAppCommand
{ {
Guid IAppCommand.AppId Guid IAppCommand.AppId
{ {

3
src/Squidex.Write/AppCommand.cs

@ -7,11 +7,10 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Squidex.Infrastructure.CQRS.Commands;
namespace Squidex.Write namespace Squidex.Write
{ {
public abstract class AppCommand : AggregateCommand, IAppCommand public abstract class AppCommand : SquidexCommand, IAppCommand
{ {
public Guid AppId { get; set; } public Guid AppId { get; set; }
} }

57
src/Squidex.Write/Apps/AppCommandHandler.cs

@ -16,68 +16,71 @@ using Squidex.Write.Apps.Commands;
namespace Squidex.Write.Apps namespace Squidex.Write.Apps
{ {
public class AppCommandHandler : CommandHandler<AppDomainObject> public class AppCommandHandler : ICommandHandler
{ {
private readonly IAggregateHandler handler;
private readonly IAppRepository appRepository; private readonly IAppRepository appRepository;
private readonly IUserRepository userRepository; private readonly IUserRepository userRepository;
private readonly ClientKeyGenerator keyGenerator; private readonly ClientKeyGenerator keyGenerator;
public AppCommandHandler( public AppCommandHandler(
IDomainObjectFactory domainObjectFactory, IAggregateHandler handler,
IDomainObjectRepository domainObjectRepository,
IUserRepository userRepository,
IAppRepository appRepository, IAppRepository appRepository,
IUserRepository userRepository,
ClientKeyGenerator keyGenerator) ClientKeyGenerator keyGenerator)
: base(domainObjectFactory, domainObjectRepository)
{ {
Guard.NotNull(handler, nameof(handler));
Guard.NotNull(keyGenerator, nameof(keyGenerator)); Guard.NotNull(keyGenerator, nameof(keyGenerator));
Guard.NotNull(appRepository, nameof(appRepository)); Guard.NotNull(appRepository, nameof(appRepository));
Guard.NotNull(userRepository, nameof(userRepository)); Guard.NotNull(userRepository, nameof(userRepository));
this.handler = handler;
this.keyGenerator = keyGenerator; this.keyGenerator = keyGenerator;
this.appRepository = appRepository; this.appRepository = appRepository;
this.userRepository = userRepository; this.userRepository = userRepository;
} }
protected Task On(CreateApp command, CommandContext context) protected async Task On(CreateApp command, CommandContext context)
{ {
return CreateAsync(command, async x => if (await appRepository.FindAppByNameAsync(command.Name) != null)
{ {
if (await appRepository.FindAppByNameAsync(command.Name) != null) var error =
{ new ValidationError($"A app with name '{command.Name}' already exists",
var error = new ValidationError($"A app with name '{command.Name}' already exists", nameof(CreateApp.Name)); nameof(CreateApp.Name));
throw new ValidationException("Cannot create a new app", error); throw new ValidationException("Cannot create a new app", error);
} }
await handler.CreateAsync<AppDomainObject>(command, x =>
{
x.Create(command); x.Create(command);
context.Succeed(command.AggregateId); context.Succeed(command.AggregateId);
}); });
} }
protected Task On(AssignContributor command, CommandContext context) protected async Task On(AssignContributor command, CommandContext context)
{ {
return UpdateAsync(command, async x => if (await userRepository.FindUserByIdAsync(command.ContributorId) == null)
{ {
if (await userRepository.FindUserByIdAsync(command.ContributorId) == null) var error =
{ new ValidationError($"Cannot find contributor '{command.ContributorId ?? "UNKNOWN"}'",
var error = new ValidationError($"Cannot find contributor '{command.ContributorId ?? "UNKNOWN"}'", nameof(AssignContributor.ContributorId)); nameof(AssignContributor.ContributorId));
throw new ValidationException("Cannot assign contributor to app", error); throw new ValidationException("Cannot assign contributor to app", error);
} }
await handler.UpdateAsync<AppDomainObject>(command, x =>
{
x.AssignContributor(command); x.AssignContributor(command);
}); });
} }
protected Task On(AttachClient command, CommandContext context) protected Task On(AttachClient command, CommandContext context)
{ {
return UpdateAsync(command, x => return handler.UpdateAsync<AppDomainObject>(command, x =>
{ {
var clientKey = keyGenerator.GenerateKey(); x.AttachClient(command, keyGenerator.GenerateKey());
x.AttachClient(command, clientKey);
context.Succeed(x.Clients[command.ClientName]); context.Succeed(x.Clients[command.ClientName]);
}); });
@ -85,20 +88,20 @@ namespace Squidex.Write.Apps
protected Task On(RemoveContributor command, CommandContext context) protected Task On(RemoveContributor command, CommandContext context)
{ {
return UpdateAsync(command, x => x.RemoveContributor(command)); return handler.UpdateAsync<AppDomainObject>(command, x => x.RemoveContributor(command));
} }
protected Task On(RevokeClient command, CommandContext context) protected Task On(RevokeClient command, CommandContext context)
{ {
return UpdateAsync(command, x => x.RevokeClient(command)); return handler.UpdateAsync<AppDomainObject>(command, x => x.RevokeClient(command));
} }
protected Task On(ConfigureLanguages command, CommandContext context) protected Task On(ConfigureLanguages command, CommandContext context)
{ {
return UpdateAsync(command, x => x.ConfigureLanguages(command)); return handler.UpdateAsync<AppDomainObject>(command, x => x.ConfigureLanguages(command));
} }
public override Task<bool> HandleAsync(CommandContext context) public Task<bool> HandleAsync(CommandContext context)
{ {
return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command, context); return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command, context);
} }

32
src/Squidex.Write/EnrichWithAppIdProcessor.cs

@ -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;
}
}
}

15
src/Squidex.Write/Schemas/Commands/CreateSchema.cs

@ -6,6 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Squidex.Core.Schemas; using Squidex.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -16,15 +17,23 @@ namespace Squidex.Write.Schemas.Commands
{ {
private SchemaProperties properties; private SchemaProperties properties;
public string Name { get; set; }
public SchemaProperties Properties public SchemaProperties Properties
{ {
get get
{ {
return properties ?? (properties = new SchemaProperties()); return properties ?? (properties = new SchemaProperties());
} }
set { properties = value; } set
{
properties = value;
}
}
public string Name { get; set; }
public CreateSchema()
{
AggregateId = Guid.NewGuid();
} }
public void Validate(IList<ValidationError> errors) public void Validate(IList<ValidationError> errors)

46
src/Squidex.Write/Schemas/SchemaCommandHandler.cs

@ -16,28 +16,32 @@ using Squidex.Write.Schemas.Commands;
namespace Squidex.Write.Schemas namespace Squidex.Write.Schemas
{ {
public class SchemaCommandHandler : CommandHandler<SchemaDomainObject> public class SchemaCommandHandler : ICommandHandler
{ {
private readonly ISchemaProvider schemaProvider; private readonly ISchemaProvider schemas;
private readonly IAggregateHandler handler;
public SchemaCommandHandler( public SchemaCommandHandler(IAggregateHandler handler, ISchemaProvider schemas)
IDomainObjectFactory domainObjectFactory,
IDomainObjectRepository domainObjectRepository,
ISchemaProvider schemaProvider)
: base(domainObjectFactory, domainObjectRepository)
{ {
this.schemaProvider = schemaProvider; Guard.NotNull(handler, nameof(handler));
Guard.NotNull(schemas, nameof(schemas));
this.handler = handler;
this.schemas = schemas;
} }
protected async Task On(CreateSchema command, CommandContext context) protected async Task On(CreateSchema command, CommandContext context)
{ {
if (await schemaProvider.FindSchemaIdByNameAsync(command.AppId, command.Name) != null) if (await schemas.FindSchemaIdByNameAsync(command.AppId, command.Name) != null)
{ {
var error = new ValidationError($"A schema with name '{command.Name}' already exists", "DisplayName"); var error =
new ValidationError($"A schema with name '{command.Name}' already exists", "DisplayName",
nameof(CreateSchema.Name));
throw new ValidationException("Cannot create a new schema", error); throw new ValidationException("Cannot create a new schema", error);
} }
await CreateAsync(command, s =>
await handler.CreateAsync<SchemaDomainObject>(command, s =>
{ {
s.Create(command); s.Create(command);
@ -47,7 +51,7 @@ namespace Squidex.Write.Schemas
protected Task On(AddField command, CommandContext context) protected Task On(AddField command, CommandContext context)
{ {
return UpdateAsync(command, s => return handler.UpdateAsync<SchemaDomainObject>(command, s =>
{ {
s.AddField(command); s.AddField(command);
@ -57,45 +61,45 @@ namespace Squidex.Write.Schemas
protected Task On(DeleteSchema command, CommandContext context) protected Task On(DeleteSchema command, CommandContext context)
{ {
return UpdateAsync(command, s => s.Delete(command)); return handler.UpdateAsync<SchemaDomainObject>(command, s => s.Delete(command));
} }
protected Task On(DeleteField command, CommandContext context) protected Task On(DeleteField command, CommandContext context)
{ {
return UpdateAsync(command, s => s.DeleteField(command)); return handler.UpdateAsync<SchemaDomainObject>(command, s => s.DeleteField(command));
} }
protected Task On(DisableField command, CommandContext context) protected Task On(DisableField command, CommandContext context)
{ {
return UpdateAsync(command, s => s.DisableField(command)); return handler.UpdateAsync<SchemaDomainObject>(command, s => s.DisableField(command));
} }
protected Task On(EnableField command, CommandContext context) protected Task On(EnableField command, CommandContext context)
{ {
return UpdateAsync(command, s => s.EnableField(command)); return handler.UpdateAsync<SchemaDomainObject>(command, s => s.EnableField(command));
} }
protected Task On(HideField command, CommandContext context) protected Task On(HideField command, CommandContext context)
{ {
return UpdateAsync(command, s => s.HideField(command)); return handler.UpdateAsync<SchemaDomainObject>(command, s => s.HideField(command));
} }
protected Task On(ShowField command, CommandContext context) protected Task On(ShowField command, CommandContext context)
{ {
return UpdateAsync(command, s => s.ShowField(command)); return handler.UpdateAsync<SchemaDomainObject>(command, s => s.ShowField(command));
} }
protected Task On(UpdateSchema command, CommandContext context) protected Task On(UpdateSchema command, CommandContext context)
{ {
return UpdateAsync(command, s => s.Update(command)); return handler.UpdateAsync<SchemaDomainObject>(command, s => s.Update(command));
} }
protected Task On(UpdateField command, CommandContext context) protected Task On(UpdateField command, CommandContext context)
{ {
return UpdateAsync(command, s => { s.UpdateField(command); }); return handler.UpdateAsync<SchemaDomainObject>(command, s => { s.UpdateField(command); });
} }
public override Task<bool> HandleAsync(CommandContext context) public Task<bool> HandleAsync(CommandContext context)
{ {
return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command, context); return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command, context);
} }

10
src/Squidex.Write/Schemas/SchemaDomainObject.cs

@ -18,10 +18,9 @@ using Squidex.Write.Schemas.Commands;
namespace Squidex.Write.Schemas namespace Squidex.Write.Schemas
{ {
public class SchemaDomainObject : DomainObject, IAppAggregate public class SchemaDomainObject : DomainObject
{ {
private readonly FieldRegistry registry; private readonly FieldRegistry registry;
private Guid appId;
private bool isDeleted; private bool isDeleted;
private long totalFields; private long totalFields;
private Schema schema; private Schema schema;
@ -31,11 +30,6 @@ namespace Squidex.Write.Schemas
get { return schema; } get { return schema; }
} }
public Guid AppId
{
get { return appId; }
}
public bool IsDeleted public bool IsDeleted
{ {
get { return isDeleted; } get { return isDeleted; }
@ -58,8 +52,6 @@ namespace Squidex.Write.Schemas
public void On(SchemaCreated @event) public void On(SchemaCreated @event)
{ {
appId = @event.AppId;
schema = Schema.Create(@event.Name, @event.Properties); schema = Schema.Create(@event.Name, @event.Properties);
} }

11
src/Squidex.Infrastructure/CQRS/IAppAggregate.cs → src/Squidex.Write/SquidexCommand.cs

@ -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; }
} }
} }

14
src/Squidex/Config/Domain/WriteModule.cs

@ -7,7 +7,9 @@
// ========================================================================== // ==========================================================================
using Autofac; using Autofac;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Pipeline.CommandHandlers; using Squidex.Pipeline.CommandHandlers;
using Squidex.Write;
using Squidex.Write.Apps; using Squidex.Write.Apps;
using Squidex.Write.Schemas; using Squidex.Write.Schemas;
@ -33,6 +35,18 @@ namespace Squidex.Config.Domain
.As<ICommandHandler>() .As<ICommandHandler>()
.SingleInstance(); .SingleInstance();
builder.RegisterType<EnrichWithAppIdProcessor>()
.As<IEventProcessor>()
.SingleInstance();
builder.RegisterType<EnrichWithAggregateIdProcessor>()
.As<IEventProcessor>()
.SingleInstance();
builder.RegisterType<EnrichWithUserHandler>()
.As<IEventProcessor>()
.SingleInstance();
builder.RegisterType<AppCommandHandler>() builder.RegisterType<AppCommandHandler>()
.As<ICommandHandler>() .As<ICommandHandler>()
.SingleInstance(); .SingleInstance();

209
tests/Squidex.Infrastructure.Tests/CQRS/Commands/AggregateHandlerTests.cs

@ -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();
}
}
}

4
tests/Squidex.Infrastructure.Tests/CQRS/Commands/CommandContextTests.cs

@ -13,9 +13,9 @@ namespace Squidex.Infrastructure.CQRS.Commands
{ {
public class CommandContextTests public class CommandContextTests
{ {
private readonly MockupCommand command = new MockupCommand(); private readonly MyCommand command = new MyCommand();
private sealed class MockupCommand : ICommand private sealed class MyCommand : ICommand
{ {
} }

129
tests/Squidex.Infrastructure.Tests/CQRS/Commands/CommandsHandlerTests.cs

@ -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);
}
}
}

10
tests/Squidex.Infrastructure.Tests/CQRS/Commands/EnrichWithTimestampHandlerTests.cs

@ -14,11 +14,11 @@ namespace Squidex.Infrastructure.CQRS.Commands
{ {
public sealed class EnrichWithTimestampHandlerTests public sealed class EnrichWithTimestampHandlerTests
{ {
private sealed class NormalCommand : AggregateCommand private sealed class MyNormalCommand : AggregateCommand
{ {
} }
private sealed class TimestampCommand : AggregateCommand, ITimestampCommand private sealed class MyTimestampCommand : AggregateCommand, ITimestampCommand
{ {
public DateTime Timestamp { get; set; } public DateTime Timestamp { get; set; }
} }
@ -29,7 +29,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
var utc = DateTime.Today; var utc = DateTime.Today;
var sut = new EnrichWithTimestampHandler(() => utc); var sut = new EnrichWithTimestampHandler(() => utc);
var command = new TimestampCommand(); var command = new MyTimestampCommand();
var result = await sut.HandleAsync(new CommandContext(command)); var result = await sut.HandleAsync(new CommandContext(command));
@ -43,7 +43,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var sut = new EnrichWithTimestampHandler(); var sut = new EnrichWithTimestampHandler();
var command = new TimestampCommand(); var command = new MyTimestampCommand();
var result = await sut.HandleAsync(new CommandContext(command)); var result = await sut.HandleAsync(new CommandContext(command));
@ -57,7 +57,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
var utc = DateTime.Today; var utc = DateTime.Today;
var sut = new EnrichWithTimestampHandler(() => utc); var sut = new EnrichWithTimestampHandler(() => utc);
var result = await sut.HandleAsync(new CommandContext(new NormalCommand())); var result = await sut.HandleAsync(new CommandContext(new MyNormalCommand()));
Assert.False(result); Assert.False(result);
} }

4
tests/Squidex.Infrastructure.Tests/CQRS/Commands/InMemoryCommandBusTests.cs

@ -14,9 +14,9 @@ namespace Squidex.Infrastructure.CQRS.Commands
{ {
public class InMemoryCommandBusTests public class InMemoryCommandBusTests
{ {
private readonly TestCommand command = new TestCommand(); private readonly MyCommand command = new MyCommand();
private sealed class TestCommand : ICommand private sealed class MyCommand : ICommand
{ {
} }

10
tests/Squidex.Infrastructure.Tests/CQRS/DomainObjectTest.cs

@ -17,7 +17,7 @@ namespace Squidex.Infrastructure.CQRS
{ {
public class DomainObjectTest public class DomainObjectTest
{ {
private sealed class Event : IEvent private sealed class MyEvent : IEvent
{ {
} }
@ -52,8 +52,8 @@ namespace Squidex.Infrastructure.CQRS
[Fact] [Fact]
public void Should_add_event_to_uncommitted_events_and_increase_version_when_raised() public void Should_add_event_to_uncommitted_events_and_increase_version_when_raised()
{ {
var event1 = new Event(); var event1 = new MyEvent();
var event2 = new Event(); var event2 = new MyEvent();
var sut = new UserDomainObject(Guid.NewGuid(), 10); var sut = new UserDomainObject(Guid.NewGuid(), 10);
@ -74,8 +74,8 @@ namespace Squidex.Infrastructure.CQRS
[Fact] [Fact]
public void Should_not_add_event_to_uncommitted_events_and_increase_version_when_raised() public void Should_not_add_event_to_uncommitted_events_and_increase_version_when_raised()
{ {
var event1 = new Event(); var event1 = new MyEvent();
var event2 = new Event(); var event2 = new MyEvent();
var sut = new UserDomainObject(Guid.NewGuid(), 10); var sut = new UserDomainObject(Guid.NewGuid(), 10);

10
tests/Squidex.Infrastructure.Tests/CQRS/EnvelopeExtensionsTests.cs

@ -63,14 +63,14 @@ namespace Squidex.Infrastructure.CQRS
} }
[Fact] [Fact]
public void Should_set_and_get_app_id() public void Should_set_and_get_user()
{ {
var commitId = Guid.NewGuid(); var user = new UserToken("subject", "123");
sut.SetAppId(commitId); sut.SetUser(user);
Assert.Equal(commitId, sut.Headers.AppId()); Assert.Equal(user, sut.Headers.User());
Assert.Equal(commitId, sut.Headers["AppId"].ToGuid(culture)); Assert.Equal(user, UserToken.Parse(sut.Headers["User"].ToString()));
} }
[Fact] [Fact]

20
tests/Squidex.Infrastructure.Tests/CQRS/EventStore/DefaultNameResolverTests.cs

@ -14,9 +14,9 @@ namespace Squidex.Infrastructure.CQRS.EventStore
{ {
public class DefaultNameResolverTests public class DefaultNameResolverTests
{ {
private sealed class User : DomainObject private sealed class MyUser : DomainObject
{ {
public User(Guid id, int version) public MyUser(Guid id, int version)
: base(id, version) : base(id, version)
{ {
} }
@ -26,9 +26,9 @@ namespace Squidex.Infrastructure.CQRS.EventStore
} }
} }
private sealed class UserDomainObject : DomainObject private sealed class MyUserDomainObject : DomainObject
{ {
public UserDomainObject(Guid id, int version) public MyUserDomainObject(Guid id, int version)
: base(id, version) : base(id, version)
{ {
} }
@ -42,22 +42,22 @@ namespace Squidex.Infrastructure.CQRS.EventStore
public void Should_calculate_name() public void Should_calculate_name()
{ {
var sut = new DefaultNameResolver("Squidex"); var sut = new DefaultNameResolver("Squidex");
var user = new User(Guid.NewGuid(), 1); var user = new MyUser(Guid.NewGuid(), 1);
var name = sut.GetStreamName(typeof(User), user.Id); var name = sut.GetStreamName(typeof(MyUser), user.Id);
Assert.Equal($"squidex-user-{user.Id}", name); Assert.Equal($"squidex-myUser-{user.Id}", name);
} }
[Fact] [Fact]
public void Should_calculate_name_and_remove_suffix() public void Should_calculate_name_and_remove_suffix()
{ {
var sut = new DefaultNameResolver("Squidex"); var sut = new DefaultNameResolver("Squidex");
var user = new UserDomainObject(Guid.NewGuid(), 1); var user = new MyUserDomainObject(Guid.NewGuid(), 1);
var name = sut.GetStreamName(typeof(UserDomainObject), user.Id); var name = sut.GetStreamName(typeof(MyUserDomainObject), user.Id);
Assert.Equal($"squidex-user-{user.Id}", name); Assert.Equal($"squidex-myUser-{user.Id}", name);
} }
} }
} }

13
tests/Squidex.Infrastructure.Tests/CQRS/EventStore/EventStoreFormatterTests.cs

@ -18,12 +18,12 @@ namespace Squidex.Infrastructure.CQRS.EventStore
{ {
public class EventStoreFormatterTests public class EventStoreFormatterTests
{ {
public sealed class Event : IEvent public sealed class MyEvent : IEvent
{ {
public string MyProperty { get; set; } public string MyProperty { get; set; }
} }
public sealed class ReceivedEvent : IReceivedEvent public sealed class MyReceivedEvent : IReceivedEvent
{ {
public int EventNumber { get; set; } public int EventNumber { get; set; }
@ -45,17 +45,16 @@ namespace Squidex.Infrastructure.CQRS.EventStore
public EventStoreFormatterTests() public EventStoreFormatterTests()
{ {
TypeNameRegistry.Map(typeof(Event), "Event"); TypeNameRegistry.Map(typeof(MyEvent), "Event");
} }
[Fact] [Fact]
public void Should_serialize_and_deserialize_envelope() public void Should_serialize_and_deserialize_envelope()
{ {
var commitId = Guid.NewGuid(); var commitId = Guid.NewGuid();
var inputEvent = new Envelope<Event>(new Event { MyProperty = "My-Property" }); var inputEvent = new Envelope<MyEvent>(new MyEvent { MyProperty = "My-Property" });
inputEvent.SetAggregateId(Guid.NewGuid()); inputEvent.SetAggregateId(Guid.NewGuid());
inputEvent.SetAppId(Guid.NewGuid());
inputEvent.SetCommitId(commitId); inputEvent.SetCommitId(commitId);
inputEvent.SetEventId(Guid.NewGuid()); inputEvent.SetEventId(Guid.NewGuid());
inputEvent.SetEventNumber(1); inputEvent.SetEventNumber(1);
@ -65,7 +64,7 @@ namespace Squidex.Infrastructure.CQRS.EventStore
var eventData = sut.ToEventData(inputEvent.To<IEvent>(), commitId); var eventData = sut.ToEventData(inputEvent.To<IEvent>(), commitId);
var receivedEvent = new ReceivedEvent var receivedEvent = new MyReceivedEvent
{ {
Payload = eventData.Data, Payload = eventData.Data,
Created = inputEvent.Headers.Timestamp().ToDateTimeUtc(), Created = inputEvent.Headers.Timestamp().ToDateTimeUtc(),
@ -74,7 +73,7 @@ namespace Squidex.Infrastructure.CQRS.EventStore
Metadata = eventData.Metadata Metadata = eventData.Metadata
}; };
var outputEvent = sut.Parse(receivedEvent).To<Event>(); var outputEvent = sut.Parse(receivedEvent).To<MyEvent>();
CompareHeaders(outputEvent.Headers, inputEvent.Headers); CompareHeaders(outputEvent.Headers, inputEvent.Headers);

56
tests/Squidex.Infrastructure.Tests/CQRS/Events/EnrichWithAggregateIdProcessorTests.cs

@ -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());
}
}
}

55
tests/Squidex.Infrastructure.Tests/CQRS/Events/EnrichWithUserProcessorTests.cs

@ -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());
}
}
}

144
tests/Squidex.Infrastructure.Tests/DispatchingTests.cs

@ -19,155 +19,155 @@ namespace Squidex.Infrastructure
{ {
public sealed class DispatchingTests public sealed class DispatchingTests
{ {
private interface IEvent { } private interface IMyEvent { }
private class EventA : IEvent { } private class MyEventA : IMyEvent { }
private class EventB : IEvent { } private class MyEventB : IMyEvent { }
private class Unknown : IEvent { } private class MyUnknown : IMyEvent { }
private class AsyncFuncConsumer private class MyAsyncFuncConsumer
{ {
public int EventATriggered { get; private set; } public int EventATriggered { get; private set; }
public int EventBTriggered { get; private set; } public int EventBTriggered { get; private set; }
public Task<int> DispatchEventAsync(IEvent @event) public Task<int> DispatchEventAsync(IMyEvent @event)
{ {
return this.DispatchFuncAsync(@event, 9); return this.DispatchFuncAsync(@event, 9);
} }
public Task<int> DispatchEventAsync(IEvent @event, int context) public Task<int> DispatchEventAsync(IMyEvent @event, int context)
{ {
return this.DispatchFuncAsync(@event, context, 13); return this.DispatchFuncAsync(@event, context, 13);
} }
public Task<int> On(EventA @event) public Task<int> On(MyEventA @event)
{ {
return Task.FromResult(++EventATriggered); return Task.FromResult(++EventATriggered);
} }
public Task<int> On(EventB @event) public Task<int> On(MyEventB @event)
{ {
return Task.FromResult(++EventBTriggered); return Task.FromResult(++EventBTriggered);
} }
public Task<int> On(EventA @event, int context) public Task<int> On(MyEventA @event, int context)
{ {
return Task.FromResult(++EventATriggered + context); return Task.FromResult(++EventATriggered + context);
} }
public Task<int> On(EventB @event, int context) public Task<int> On(MyEventB @event, int context)
{ {
return Task.FromResult(++EventBTriggered + context); return Task.FromResult(++EventBTriggered + context);
} }
} }
private class AsyncConsumer private class MyAsyncConsumer
{ {
public int EventATriggered { get; private set; } public int EventATriggered { get; private set; }
public int EventBTriggered { get; private set; } public int EventBTriggered { get; private set; }
public Task<bool> DispatchEventAsync(IEvent @event) public Task<bool> DispatchEventAsync(IMyEvent @event)
{ {
return this.DispatchActionAsync(@event); return this.DispatchActionAsync(@event);
} }
public Task<bool> DispatchEventAsync(IEvent @event, int context) public Task<bool> DispatchEventAsync(IMyEvent @event, int context)
{ {
return this.DispatchActionAsync(@event, context); return this.DispatchActionAsync(@event, context);
} }
public Task On(EventA @event) public Task On(MyEventA @event)
{ {
EventATriggered++; EventATriggered++;
return TaskHelper.Done; return TaskHelper.Done;
} }
public Task On(EventB @event) public Task On(MyEventB @event)
{ {
EventBTriggered++; EventBTriggered++;
return TaskHelper.Done; return TaskHelper.Done;
} }
public Task On(EventA @event, int context) public Task On(MyEventA @event, int context)
{ {
EventATriggered = EventATriggered + context; EventATriggered = EventATriggered + context;
return TaskHelper.Done; return TaskHelper.Done;
} }
public Task On(EventB @event, int context) public Task On(MyEventB @event, int context)
{ {
EventBTriggered = EventATriggered + context; EventBTriggered = EventATriggered + context;
return TaskHelper.Done; return TaskHelper.Done;
} }
} }
private class SyncFuncConsumer private class MySyncFuncConsumer
{ {
public int EventATriggered { get; private set; } public int EventATriggered { get; private set; }
public int EventBTriggered { get; private set; } public int EventBTriggered { get; private set; }
public int DispatchEvent(IEvent @event) public int DispatchEvent(IMyEvent @event)
{ {
return this.DispatchFunc(@event, 9); return this.DispatchFunc(@event, 9);
} }
public int DispatchEvent(IEvent @event, int context) public int DispatchEvent(IMyEvent @event, int context)
{ {
return this.DispatchFunc(@event, context, 13); return this.DispatchFunc(@event, context, 13);
} }
public int On(EventA @event) public int On(MyEventA @event)
{ {
return ++EventATriggered; return ++EventATriggered;
} }
public int On(EventB @event) public int On(MyEventB @event)
{ {
return ++EventBTriggered; return ++EventBTriggered;
} }
public int On(EventA @event, int context) public int On(MyEventA @event, int context)
{ {
return ++EventATriggered + context; return ++EventATriggered + context;
} }
public int On(EventB @event, int context) public int On(MyEventB @event, int context)
{ {
return ++EventBTriggered + context; return ++EventBTriggered + context;
} }
} }
private class SyncActionConsumer private class MySyncActionConsumer
{ {
public int EventATriggered { get; private set; } public int EventATriggered { get; private set; }
public int EventBTriggered { get; private set; } public int EventBTriggered { get; private set; }
public bool DispatchEvent(IEvent @event) public bool DispatchEvent(IMyEvent @event)
{ {
return this.DispatchAction(@event); return this.DispatchAction(@event);
} }
public bool DispatchEvent(IEvent @event, int context) public bool DispatchEvent(IMyEvent @event, int context)
{ {
return this.DispatchAction(@event, context); return this.DispatchAction(@event, context);
} }
public void On(EventA @event) public void On(MyEventA @event)
{ {
EventATriggered++; EventATriggered++;
} }
public void On(EventB @event) public void On(MyEventB @event)
{ {
EventBTriggered++; EventBTriggered++;
} }
public void On(EventA @event, int context) public void On(MyEventA @event, int context)
{ {
EventATriggered = EventATriggered + context; EventATriggered = EventATriggered + context;
} }
public void On(EventB @event, int context) public void On(MyEventB @event, int context)
{ {
EventBTriggered = EventATriggered + context; EventBTriggered = EventATriggered + context;
} }
@ -176,12 +176,12 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_invoke_correct_event() public void Should_invoke_correct_event()
{ {
var consumer = new SyncActionConsumer(); var consumer = new MySyncActionConsumer();
consumer.DispatchEvent(new EventA()); consumer.DispatchEvent(new MyEventA());
consumer.DispatchEvent(new EventB()); consumer.DispatchEvent(new MyEventB());
consumer.DispatchEvent(new EventB()); consumer.DispatchEvent(new MyEventB());
consumer.DispatchEvent(new Unknown()); consumer.DispatchEvent(new MyUnknown());
Assert.Equal(1, consumer.EventATriggered); Assert.Equal(1, consumer.EventATriggered);
Assert.Equal(2, consumer.EventBTriggered); Assert.Equal(2, consumer.EventBTriggered);
@ -190,12 +190,12 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_invoke_correct_event_with_context() public void Should_invoke_correct_event_with_context()
{ {
var consumer = new SyncActionConsumer(); var consumer = new MySyncActionConsumer();
consumer.DispatchEvent(new EventA(), 2); consumer.DispatchEvent(new MyEventA(), 2);
consumer.DispatchEvent(new EventB(), 2); consumer.DispatchEvent(new MyEventB(), 2);
consumer.DispatchEvent(new EventB(), 2); consumer.DispatchEvent(new MyEventB(), 2);
consumer.DispatchEvent(new Unknown(), 2); consumer.DispatchEvent(new MyUnknown(), 2);
Assert.Equal(2, consumer.EventATriggered); Assert.Equal(2, consumer.EventATriggered);
Assert.Equal(4, consumer.EventBTriggered); Assert.Equal(4, consumer.EventBTriggered);
@ -204,12 +204,12 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public async Task Should_invoke_correct_event_asynchronously() public async Task Should_invoke_correct_event_asynchronously()
{ {
var consumer = new AsyncConsumer(); var consumer = new MyAsyncConsumer();
await consumer.DispatchEventAsync(new EventA()); await consumer.DispatchEventAsync(new MyEventA());
await consumer.DispatchEventAsync(new EventB()); await consumer.DispatchEventAsync(new MyEventB());
await consumer.DispatchEventAsync(new EventB()); await consumer.DispatchEventAsync(new MyEventB());
await consumer.DispatchEventAsync(new Unknown()); await consumer.DispatchEventAsync(new MyUnknown());
Assert.Equal(1, consumer.EventATriggered); Assert.Equal(1, consumer.EventATriggered);
Assert.Equal(2, consumer.EventBTriggered); Assert.Equal(2, consumer.EventBTriggered);
@ -218,12 +218,12 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public async Task Should_invoke_correct_event_with_context_asynchronously() public async Task Should_invoke_correct_event_with_context_asynchronously()
{ {
var consumer = new AsyncConsumer(); var consumer = new MyAsyncConsumer();
await consumer.DispatchEventAsync(new EventA(), 2); await consumer.DispatchEventAsync(new MyEventA(), 2);
await consumer.DispatchEventAsync(new EventB(), 2); await consumer.DispatchEventAsync(new MyEventB(), 2);
await consumer.DispatchEventAsync(new EventB(), 2); await consumer.DispatchEventAsync(new MyEventB(), 2);
await consumer.DispatchEventAsync(new Unknown(), 2); await consumer.DispatchEventAsync(new MyUnknown(), 2);
Assert.Equal(2, consumer.EventATriggered); Assert.Equal(2, consumer.EventATriggered);
Assert.Equal(4, consumer.EventBTriggered); Assert.Equal(4, consumer.EventBTriggered);
@ -232,12 +232,12 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_invoke_correct_event_and_return() public void Should_invoke_correct_event_and_return()
{ {
var consumer = new SyncFuncConsumer(); var consumer = new MySyncFuncConsumer();
Assert.Equal(1, consumer.DispatchEvent(new EventA())); Assert.Equal(1, consumer.DispatchEvent(new MyEventA()));
Assert.Equal(1, consumer.DispatchEvent(new EventB())); Assert.Equal(1, consumer.DispatchEvent(new MyEventB()));
Assert.Equal(2, consumer.DispatchEvent(new EventB())); Assert.Equal(2, consumer.DispatchEvent(new MyEventB()));
Assert.Equal(9, consumer.DispatchEvent(new Unknown())); Assert.Equal(9, consumer.DispatchEvent(new MyUnknown()));
Assert.Equal(1, consumer.EventATriggered); Assert.Equal(1, consumer.EventATriggered);
Assert.Equal(2, consumer.EventBTriggered); Assert.Equal(2, consumer.EventBTriggered);
@ -246,12 +246,12 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Should_invoke_correct_event_with_context_and_return() public void Should_invoke_correct_event_with_context_and_return()
{ {
var consumer = new SyncFuncConsumer(); var consumer = new MySyncFuncConsumer();
Assert.Equal(11, consumer.DispatchEvent(new EventA(), 10)); Assert.Equal(11, consumer.DispatchEvent(new MyEventA(), 10));
Assert.Equal(11, consumer.DispatchEvent(new EventB(), 10)); Assert.Equal(11, consumer.DispatchEvent(new MyEventB(), 10));
Assert.Equal(12, consumer.DispatchEvent(new EventB(), 10)); Assert.Equal(12, consumer.DispatchEvent(new MyEventB(), 10));
Assert.Equal(13, consumer.DispatchEvent(new Unknown(), 10)); Assert.Equal(13, consumer.DispatchEvent(new MyUnknown(), 10));
Assert.Equal(1, consumer.EventATriggered); Assert.Equal(1, consumer.EventATriggered);
Assert.Equal(2, consumer.EventBTriggered); Assert.Equal(2, consumer.EventBTriggered);
@ -260,12 +260,12 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public async Task Should_invoke_correct_event_and_return_synchronously() public async Task Should_invoke_correct_event_and_return_synchronously()
{ {
var consumer = new AsyncFuncConsumer(); var consumer = new MyAsyncFuncConsumer();
Assert.Equal(1, await consumer.DispatchEventAsync(new EventA())); Assert.Equal(1, await consumer.DispatchEventAsync(new MyEventA()));
Assert.Equal(1, await consumer.DispatchEventAsync(new EventB())); Assert.Equal(1, await consumer.DispatchEventAsync(new MyEventB()));
Assert.Equal(2, await consumer.DispatchEventAsync(new EventB())); Assert.Equal(2, await consumer.DispatchEventAsync(new MyEventB()));
Assert.Equal(9, await consumer.DispatchEventAsync(new Unknown())); Assert.Equal(9, await consumer.DispatchEventAsync(new MyUnknown()));
Assert.Equal(1, consumer.EventATriggered); Assert.Equal(1, consumer.EventATriggered);
Assert.Equal(2, consumer.EventBTriggered); Assert.Equal(2, consumer.EventBTriggered);
@ -274,12 +274,12 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public async Task Should_invoke_correct_event_with_context_and_return_synchronously() public async Task Should_invoke_correct_event_with_context_and_return_synchronously()
{ {
var consumer = new AsyncFuncConsumer(); var consumer = new MyAsyncFuncConsumer();
Assert.Equal(11, await consumer.DispatchEventAsync(new EventA(), 10)); Assert.Equal(11, await consumer.DispatchEventAsync(new MyEventA(), 10));
Assert.Equal(11, await consumer.DispatchEventAsync(new EventB(), 10)); Assert.Equal(11, await consumer.DispatchEventAsync(new MyEventB(), 10));
Assert.Equal(12, await consumer.DispatchEventAsync(new EventB(), 10)); Assert.Equal(12, await consumer.DispatchEventAsync(new MyEventB(), 10));
Assert.Equal(13, await consumer.DispatchEventAsync(new Unknown(), 10)); Assert.Equal(13, await consumer.DispatchEventAsync(new MyUnknown(), 10));
Assert.Equal(1, consumer.EventATriggered); Assert.Equal(1, consumer.EventATriggered);
Assert.Equal(2, consumer.EventBTriggered); Assert.Equal(2, consumer.EventBTriggered);

8
tests/Squidex.Infrastructure.Tests/GuardTests.cs

@ -12,14 +12,14 @@ using Xunit;
namespace Squidex.Infrastructure namespace Squidex.Infrastructure
{ {
public sealed class ValidatableValid : IValidatable public sealed class MyValidatableValid : IValidatable
{ {
public void Validate(IList<ValidationError> errors) public void Validate(IList<ValidationError> errors)
{ {
} }
} }
public sealed class ValidatableInvalid : IValidatable public sealed class MyValidatableInvalid : IValidatable
{ {
public void Validate(IList<ValidationError> errors) public void Validate(IList<ValidationError> errors)
{ {
@ -327,13 +327,13 @@ namespace Squidex.Infrastructure
[Fact] [Fact]
public void Valid_should_throw_if_invalid() public void Valid_should_throw_if_invalid()
{ {
Assert.Throws<ValidationException>(() => Guard.Valid(new ValidatableInvalid(), "Parameter", () => "Message")); Assert.Throws<ValidationException>(() => Guard.Valid(new MyValidatableInvalid(), "Parameter", () => "Message"));
} }
[Fact] [Fact]
public void Valid_should_do_nothing_if_valid() public void Valid_should_do_nothing_if_valid()
{ {
Guard.Valid(new ValidatableValid(), "Parameter", () => "Message"); Guard.Valid(new MyValidatableValid(), "Parameter", () => "Message");
} }
} }
} }

12
tests/Squidex.Infrastructure.Tests/Reflection/ReflectionExtensionTests.cs

@ -14,22 +14,22 @@ namespace Squidex.Infrastructure.Reflection
{ {
public class ReflectionExtensionTests public class ReflectionExtensionTests
{ {
private interface IMain : ISub1 private interface IMyMain : IMySub1
{ {
string MainProp { get; set; } string MainProp { get; set; }
} }
private interface ISub1 : ISub2 private interface IMySub1 : IMySub2
{ {
string Sub1Prop { get; set; } string Sub1Prop { get; set; }
} }
private interface ISub2 private interface IMySub2
{ {
string Sub2Prop { get; set; } string Sub2Prop { get; set; }
} }
private class Main private class MyMain
{ {
public string MainProp { get; set; } public string MainProp { get; set; }
} }
@ -37,7 +37,7 @@ namespace Squidex.Infrastructure.Reflection
[Fact] [Fact]
public void Should_find_all_public_properties_of_interfaces() public void Should_find_all_public_properties_of_interfaces()
{ {
var properties = typeof(IMain).GetPublicProperties().Select(x => x.Name).OrderBy(x => x).ToArray(); var properties = typeof(IMyMain).GetPublicProperties().Select(x => x.Name).OrderBy(x => x).ToArray();
Assert.Equal(new [] { "MainProp", "Sub1Prop", "Sub2Prop" }, properties); Assert.Equal(new [] { "MainProp", "Sub1Prop", "Sub2Prop" }, properties);
} }
@ -45,7 +45,7 @@ namespace Squidex.Infrastructure.Reflection
[Fact] [Fact]
public void Should_find_all_public_properties_of_classes() public void Should_find_all_public_properties_of_classes()
{ {
var properties = typeof(Main).GetPublicProperties().Select(x => x.Name).OrderBy(x => x).ToArray(); var properties = typeof(MyMain).GetPublicProperties().Select(x => x.Name).OrderBy(x => x).ToArray();
Assert.Equal(new[] { "MainProp" }, properties); Assert.Equal(new[] { "MainProp" }, properties);
} }

22
tests/Squidex.Infrastructure.Tests/Reflection/SimpleMapperTests.cs

@ -14,7 +14,7 @@ namespace Squidex.Infrastructure.Reflection
{ {
public class SimpleMapperTests public class SimpleMapperTests
{ {
public class Class1Base public class MyClass1Base
{ {
public string MappedString { get; set; } public string MappedString { get; set; }
@ -29,12 +29,12 @@ namespace Squidex.Infrastructure.Reflection
public long WrongType2 { get; set; } public long WrongType2 { get; set; }
} }
public class Class1 : Class1Base public class MyClass1 : MyClass1Base
{ {
public string UnmappedString { get; set; } public string UnmappedString { get; set; }
} }
public class Class2Base public class MyClass2Base
{ {
public string MappedString { get; protected set; } public string MappedString { get; protected set; }
@ -45,7 +45,7 @@ namespace Squidex.Infrastructure.Reflection
public string MappedGuid { get; set; } public string MappedGuid { get; set; }
} }
public class Class2 : Class2Base public class MyClass2 : MyClass2Base
{ {
public string UnmappedString public string UnmappedString
{ {
@ -60,19 +60,19 @@ namespace Squidex.Infrastructure.Reflection
[Fact] [Fact]
public void Should_throw_if_mapping_with_null_source() public void Should_throw_if_mapping_with_null_source()
{ {
Assert.Throws<ArgumentNullException>(() => SimpleMapper.Map((Class1)null, new Class2())); Assert.Throws<ArgumentNullException>(() => SimpleMapper.Map((MyClass1)null, new MyClass2()));
} }
[Fact] [Fact]
public void Should_throw_if_mapping_with_null_target() public void Should_throw_if_mapping_with_null_target()
{ {
Assert.Throws<ArgumentNullException>(() => SimpleMapper.Map(new Class1(), (Class2)null)); Assert.Throws<ArgumentNullException>(() => SimpleMapper.Map(new MyClass1(), (MyClass2)null));
} }
[Fact] [Fact]
public void Should_to_type() public void Should_to_type()
{ {
var class1 = new Class1 var class1 = new MyClass1
{ {
UnmappedString = Guid.NewGuid().ToString(), UnmappedString = Guid.NewGuid().ToString(),
MappedString = Guid.NewGuid().ToString(), MappedString = Guid.NewGuid().ToString(),
@ -80,7 +80,7 @@ namespace Squidex.Infrastructure.Reflection
MappedGuid = Guid.NewGuid() MappedGuid = Guid.NewGuid()
}; };
var class2 = SimpleMapper.Map<Class1, Class2>(class1); var class2 = SimpleMapper.Map<MyClass1, MyClass2>(class1);
AssertObjectEquality(class1, class2); AssertObjectEquality(class1, class2);
} }
@ -88,21 +88,21 @@ namespace Squidex.Infrastructure.Reflection
[Fact] [Fact]
public void Should_map_between_types() public void Should_map_between_types()
{ {
var class1 = new Class1 var class1 = new MyClass1
{ {
UnmappedString = Guid.NewGuid().ToString(), UnmappedString = Guid.NewGuid().ToString(),
MappedString = Guid.NewGuid().ToString(), MappedString = Guid.NewGuid().ToString(),
MappedNumber = 123, MappedNumber = 123,
MappedGuid = Guid.NewGuid() MappedGuid = Guid.NewGuid()
}; };
var class2 = new Class2(); var class2 = new MyClass2();
SimpleMapper.Map(class1, class2); SimpleMapper.Map(class1, class2);
AssertObjectEquality(class1, class2); AssertObjectEquality(class1, class2);
} }
private static void AssertObjectEquality(Class1 class1, Class2 class2) private static void AssertObjectEquality(MyClass1 class1, MyClass2 class2)
{ {
Assert.Equal(class1.MappedString, class2.MappedString); Assert.Equal(class1.MappedString, class2.MappedString);
Assert.Equal(class1.MappedNumber, class2.MappedNumber); Assert.Equal(class1.MappedNumber, class2.MappedNumber);

7
tests/Squidex.Write.Tests/Apps/AppCommandHandlerTests.cs

@ -43,12 +43,7 @@ namespace Squidex.Write.Apps
{ {
app = new AppDomainObject(Id, 0); app = new AppDomainObject(Id, 0);
sut = new AppCommandHandler( sut = new AppCommandHandler(Handler, appRepository.Object, userRepository.Object, keyGenerator.Object);
DomainObjectFactory.Object,
DomainObjectRepository.Object,
userRepository.Object,
appRepository.Object,
keyGenerator.Object);
} }
[Fact] [Fact]

58
tests/Squidex.Write.Tests/EnrichWithAppIdProcessorTests.cs

@ -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());
}
}
}

5
tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs

@ -41,10 +41,7 @@ namespace Squidex.Write.Schemas
{ {
schema = new SchemaDomainObject(Id, 0, registry); schema = new SchemaDomainObject(Id, 0, registry);
sut = new SchemaCommandHandler( sut = new SchemaCommandHandler(Handler, schemaProvider.Object);
DomainObjectFactory.Object,
DomainObjectRepository.Object,
schemaProvider.Object);
} }
[Fact] [Fact]

2
tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs

@ -62,7 +62,7 @@ namespace Squidex.Write.Schemas
.ShouldBeEquivalentTo( .ShouldBeEquivalentTo(
new IEvent[] new IEvent[]
{ {
new SchemaCreated { Name = appName, AppId = appId, Properties = properties } new SchemaCreated { Name = appName, Properties = properties }
}); });
} }

62
tests/Squidex.Write.Tests/Utils/HandlerTestBase.cs

@ -8,7 +8,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Moq;
using Squidex.Infrastructure.CQRS; using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
@ -16,8 +15,37 @@ namespace Squidex.Write.Utils
{ {
public abstract class HandlerTestBase<T> where T : DomainObject public abstract class HandlerTestBase<T> where T : DomainObject
{ {
private readonly Mock<IDomainObjectFactory> domainObjectFactory = new Mock<IDomainObjectFactory>(); private sealed class MockupHandler : IAggregateHandler
private readonly Mock<IDomainObjectRepository> domainObjectRepository = new Mock<IDomainObjectRepository>(); {
private T domainObject;
public bool IsCreated { get; private set; }
public bool IsUpdated { get; private set; }
public void Init(T newDomainObject)
{
domainObject = newDomainObject;
IsCreated = false;
IsUpdated = false;
}
public Task CreateAsync<V>(IAggregateCommand command, Func<V, Task> creator) where V : class, IAggregate
{
IsCreated = true;
return creator(domainObject as V);
}
public Task UpdateAsync<V>(IAggregateCommand command, Func<V, Task> updater) where V : class, IAggregate
{
IsUpdated = true;
return updater(domainObject as V);
}
}
private readonly MockupHandler handler = new MockupHandler();
private readonly Guid id = Guid.NewGuid(); private readonly Guid id = Guid.NewGuid();
protected Guid Id protected Guid Id
@ -25,40 +53,32 @@ namespace Squidex.Write.Utils
get { return id; } get { return id; }
} }
protected Mock<IDomainObjectFactory> DomainObjectFactory protected IAggregateHandler Handler
{
get { return domainObjectFactory; }
}
protected Mock<IDomainObjectRepository> DomainObjectRepository
{ {
get { return domainObjectRepository; } get { return handler; }
} }
public async Task TestCreate(T domainObject, Func<T, Task> action, bool succeeded = true) public async Task TestCreate(T domainObject, Func<T, Task> action, bool shouldCreate = true)
{ {
domainObjectFactory.Setup(x => x.CreateNew(typeof(T), id)).Returns(domainObject).Verifiable(); handler.Init(domainObject);
domainObjectRepository.Setup(x => x.SaveAsync(domainObject, It.IsAny<Guid>())).Returns(Task.FromResult(true)).Verifiable();
await action(domainObject); await action(domainObject);
if (succeeded) if (!handler.IsCreated && shouldCreate)
{ {
domainObjectFactory.VerifyAll(); throw new InvalidOperationException("Create not called");
domainObjectRepository.VerifyAll();
} }
} }
public async Task TestUpdate(T domainObject, Func<T, Task> action, bool succeeded = true) public async Task TestUpdate(T domainObject, Func<T, Task> action, bool shouldUpdate = true)
{ {
domainObjectRepository.Setup(x => x.GetByIdAsync<T>(domainObject.Id, int.MaxValue)).Returns(Task.FromResult(domainObject)).Verifiable(); handler.Init(domainObject);
domainObjectRepository.Setup(x => x.SaveAsync(domainObject, It.IsAny<Guid>())).Returns(Task.FromResult(true)).Verifiable();
await action(domainObject); await action(domainObject);
if (succeeded) if (!handler.IsUpdated && shouldUpdate)
{ {
domainObjectRepository.VerifyAll(); throw new InvalidOperationException("Create not called");
} }
} }
} }

Loading…
Cancel
Save