// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Teams; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.TestHelpers; public abstract class HandlerTestBase : GivenContext { private readonly IPersistenceFactory persistenceFactory = A.Fake>(); private readonly IPersistence persistence = A.Fake>(); protected abstract DomainId Id { get; } public IPersistenceFactory PersistenceFactory { get => persistenceFactory; } public IEnumerable> LastEvents { get; private set; } = Enumerable.Empty>(); protected HandlerTestBase() { #pragma warning disable MA0056 // Do not call overridable members in constructor A.CallTo(() => persistenceFactory.WithSnapshotsAndEventSourcing(A._, Id, A>._, A._)) .Returns(persistence); A.CallTo(() => persistenceFactory.WithEventSourcing(A._, Id, A._)) .Returns(persistence); A.CallTo(() => persistence.WriteEventsAsync(A>>._, CancellationToken)) .Invokes((IReadOnlyList> events, CancellationToken _) => LastEvents = events); A.CallTo(() => persistence.DeleteAsync(CancellationToken)) .Invokes(() => LastEvents = Enumerable.Empty>()); #pragma warning restore MA0056 // Do not call overridable members in constructor } protected CommandContext CreateCommandContext(TCommand command) where TCommand : IAggregateCommand { return new CommandContext(CreateCommand(command), A.Dummy()); } protected async Task HandleAsync(ICommandMiddleware middleware, TCommand command, CancellationToken ct = default) where TCommand : IAggregateCommand { var context = new CommandContext(CreateCommand(command), A.Dummy()); await middleware.HandleAsync(context, ct); return context; } protected async Task PublishAsync(DomainObject domainObject, IAggregateCommand command) where T : Entity, new() { LastEvents = []; return await ExecuteCoreAsync(domainObject, command); } protected async Task PublishIdempotentAsync(DomainObject domainObject, IAggregateCommand command) where T : Entity, new() { LastEvents = []; var actual = await ExecuteCoreAsync(domainObject, command); var previousSnapshot = domainObject.Snapshot; var previousVersion = domainObject.Snapshot.Version; await ExecuteCoreAsync(domainObject, command); Assert.Same(previousSnapshot, domainObject.Snapshot); Assert.Equal(previousVersion, domainObject.Snapshot.Version); return actual; } private async Task ExecuteCoreAsync(DomainObject domainObject, IAggregateCommand command) where T : Entity, new() { var actual = await domainObject.ExecuteAsync(CreateCommand(command), CancellationToken); return actual.Payload; } protected virtual IAggregateCommand CreateCommand(IAggregateCommand command) { if (command is SquidexCommand baseCommand) { baseCommand.ExpectedVersion = EtagVersion.Any; baseCommand.Actor ??= User; if (baseCommand.User == null && baseCommand.Actor.IsUser) { baseCommand.User = ApiContext.UserPrincipal; } } if (command is IAppCommand { AppId: null } appCommand) { appCommand.AppId = AppId; } if (command is ISchemaCommand { SchemaId: null } schemaCommand) { schemaCommand.SchemaId = SchemaId; } if (command is ITeamCommand teamCommand && teamCommand.TeamId == default) { teamCommand.TeamId = TeamId; } return command; } protected TEvent CreateEvent(TEvent @event, bool fromClient = false) where TEvent : SquidexEvent { @event.Actor = fromClient ? Client : User; if (@event is AppEvent appEvent) { appEvent.AppId = AppId; } if (@event is SchemaEvent schemaEvent) { schemaEvent.SchemaId = SchemaId; } if (@event is TeamEvent teamEvent && teamEvent.TeamId == default) { teamEvent.TeamId = TeamId; } return @event; } }