From 0b381df408445ea060005cff21dab2139279a1b9 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 5 Dec 2017 18:22:03 +0100 Subject: [PATCH] Tests for rules. --- .../SquidexCommand.cs | 3 - .../Commands/IAggregate.cs | 27 -- .../Apps/AppDomainObjectTests.cs | 2 + .../Guards/Actions/WebhookActionTests.cs | 48 ++++ .../Rules/Guards/GuardRuleTests.cs | 167 ++++++++++++ .../Triggers/ContentChangedTriggerTests.cs | 84 ++++++ .../Rules/RuleCommandMiddlewareTests.cs | 116 ++++++++ .../Rules/RuleDomainObjectTests.cs | 249 ++++++++++++++++++ 8 files changed, 666 insertions(+), 30 deletions(-) delete mode 100644 src/Squidex.Infrastructure/Commands/IAggregate.cs create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/WebhookActionTests.cs create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs diff --git a/src/Squidex.Domain.Apps.Entities/SquidexCommand.cs b/src/Squidex.Domain.Apps.Entities/SquidexCommand.cs index 58e1087a0..ae4c6cb8d 100644 --- a/src/Squidex.Domain.Apps.Entities/SquidexCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/SquidexCommand.cs @@ -6,7 +6,6 @@ // All rights reserved. // ========================================================================== -using System.Security.Claims; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -14,8 +13,6 @@ namespace Squidex.Domain.Apps.Entities { public abstract class SquidexCommand : ICommand { - public ClaimsPrincipal Principal { get; set; } - public RefToken Actor { get; set; } public long? ExpectedVersion { get; set; } diff --git a/src/Squidex.Infrastructure/Commands/IAggregate.cs b/src/Squidex.Infrastructure/Commands/IAggregate.cs deleted file mode 100644 index abc522e2a..000000000 --- a/src/Squidex.Infrastructure/Commands/IAggregate.cs +++ /dev/null @@ -1,27 +0,0 @@ -// ========================================================================== -// IAggregate.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using Squidex.Infrastructure.EventSourcing; - -namespace Squidex.Infrastructure.Commands -{ - public interface IAggregate - { - Guid Id { get; } - - int Version { get; } - - void ApplyEvent(Envelope @event); - - void ClearUncommittedEvents(); - - ICollection> GetUncomittedEvents(); - } -} \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs index 801feffba..acac9d548 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs @@ -273,6 +273,8 @@ namespace Squidex.Domain.Apps.Entities.Apps sut.UpdateLanguage(CreateCommand(new UpdateLanguage { Language = Language.DE, Fallback = new List { Language.EN } })); + Assert.True(sut.State.LanguagesConfig.Contains(Language.DE)); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateEvent(new AppLanguageUpdated { Language = Language.DE, Fallback = new List { Language.EN } }) diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/WebhookActionTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/WebhookActionTests.cs new file mode 100644 index 000000000..fed8f64ba --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/WebhookActionTests.cs @@ -0,0 +1,48 @@ +// ========================================================================== +// WebhookActionTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Xunit; + +namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions +{ + public sealed class WebhookActionTests + { + [Fact] + public async Task Should_add_error_if_url_is_null() + { + var action = new WebhookAction { Url = null }; + + var errors = await RuleActionValidator.ValidateAsync(action); + + Assert.NotEmpty(errors); + } + + [Fact] + public async Task Should_add_error_if_url_is_relative() + { + var action = new WebhookAction { Url = new Uri("/invalid", UriKind.Relative) }; + + var errors = await RuleActionValidator.ValidateAsync(action); + + Assert.NotEmpty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_url_is_absolute() + { + var action = new WebhookAction { Url = new Uri("https://squidex.io", UriKind.Absolute) }; + + var errors = await RuleActionValidator.ValidateAsync(action); + + Assert.Empty(errors); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs new file mode 100644 index 000000000..949759999 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs @@ -0,0 +1,167 @@ +// ========================================================================== +// GuardRuleTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Immutable; +using System.Threading.Tasks; +using FakeItEasy; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Entities.Rules.Commands; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; +using Xunit; + +#pragma warning disable SA1310 // Field names must not contain underscore + +namespace Squidex.Domain.Apps.Entities.Rules.Guards +{ + public class GuardRuleTests + { + private readonly Uri validUrl = new Uri("https://squidex.io"); + private readonly Rule rule_0 = new Rule(new ContentChangedTrigger(), new WebhookAction()); + private readonly NamedId appId = new NamedId(Guid.NewGuid(), "my-app"); + private readonly IAppProvider appProvider = A.Fake(); + + public GuardRuleTests() + { + A.CallTo(() => appProvider.GetSchemaAsync(appId.Name, A.Ignored, false)) + .Returns(A.Fake()); + } + + [Fact] + public async Task CanCreate_should_throw_exception_if_trigger_null() + { + var command = CreateCommand(new CreateRule + { + Trigger = null, + Action = new WebhookAction + { + Url = validUrl + } + }); + + await Assert.ThrowsAsync(() => GuardRule.CanCreate(command, appProvider)); + } + + [Fact] + public async Task CanCreate_should_throw_exception_if_action_null() + { + var command = CreateCommand(new CreateRule + { + Trigger = new ContentChangedTrigger + { + Schemas = ImmutableList.Empty + }, + Action = null + }); + + await Assert.ThrowsAsync(() => GuardRule.CanCreate(command, appProvider)); + } + + [Fact] + public async Task CanCreate_should_not_throw_exception_if_trigger_and_action_valid() + { + var command = CreateCommand(new CreateRule + { + Trigger = new ContentChangedTrigger + { + Schemas = ImmutableList.Empty + }, + Action = new WebhookAction + { + Url = validUrl + } + }); + + await GuardRule.CanCreate(command, appProvider); + } + + [Fact] + public async Task CanUpdate_should_throw_exception_if_action_and_trigger_are_null() + { + var command = new UpdateRule(); + + await Assert.ThrowsAsync(() => GuardRule.CanUpdate(command, appProvider)); + } + + [Fact] + public async Task CanUpdate_should_not_throw_exception_if_trigger_and_action_valid() + { + var command = CreateCommand(new UpdateRule + { + Trigger = new ContentChangedTrigger + { + Schemas = ImmutableList.Empty + }, + Action = new WebhookAction + { + Url = validUrl + } + }); + + await GuardRule.CanUpdate(command, appProvider); + } + + [Fact] + public void CanEnable_should_throw_exception_if_rule_enabled() + { + var command = new EnableRule(); + + var rule_1 = rule_0.Enable(); + + Assert.Throws(() => GuardRule.CanEnable(command, rule_1)); + } + + [Fact] + public void CanEnable_should_not_throw_exception_if_rule_disabled() + { + var command = new EnableRule(); + + var rule_1 = rule_0.Disable(); + + GuardRule.CanEnable(command, rule_1); + } + + [Fact] + public void CanDisable_should_throw_exception_if_rule_disabled() + { + var command = new DisableRule(); + + var rule_1 = rule_0.Disable(); + + Assert.Throws(() => GuardRule.CanDisable(command, rule_1)); + } + + [Fact] + public void CanDisable_should_not_throw_exception_if_rule_enabled() + { + var command = new DisableRule(); + + var rule_1 = rule_0.Enable(); + + GuardRule.CanDisable(command, rule_1); + } + + [Fact] + public void CanDelete_should_not_throw_exception() + { + var command = new DeleteRule(); + + GuardRule.CanDelete(command); + } + + private T CreateCommand(T command) where T : AppCommand + { + command.AppId = appId; + + return command; + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs new file mode 100644 index 000000000..cb495d264 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs @@ -0,0 +1,84 @@ +// ========================================================================== +// ContentChangedTriggerTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Immutable; +using System.Threading.Tasks; +using FakeItEasy; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Schemas; +using Xunit; + +namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers +{ + public class ContentChangedTriggerTests + { + private readonly IAppProvider appProvider = A.Fake(); + private readonly string appName = "my-app"; + + [Fact] + public async Task Should_add_error_if_schemas_ids_are_not_valid() + { + A.CallTo(() => appProvider.GetSchemaAsync(appName, A.Ignored, false)) + .Returns(Task.FromResult(null)); + + var trigger = new ContentChangedTrigger + { + Schemas = ImmutableList.Create( + new ContentChangedTriggerSchema() + ) + }; + + var errors = await RuleTriggerValidator.ValidateAsync(appName, trigger, appProvider); + + Assert.NotEmpty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_schemas_is_null() + { + var trigger = new ContentChangedTrigger(); + + var errors = await RuleTriggerValidator.ValidateAsync(appName, trigger, appProvider); + + Assert.Empty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_schemas_is_empty() + { + var trigger = new ContentChangedTrigger + { + Schemas = ImmutableList.Empty + }; + + var errors = await RuleTriggerValidator.ValidateAsync(appName, trigger, appProvider); + + Assert.Empty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_schemas_ids_are_valid() + { + A.CallTo(() => appProvider.GetSchemaAsync(appName, A.Ignored, false)) + .Returns(A.Fake()); + + var trigger = new ContentChangedTrigger + { + Schemas = ImmutableList.Create( + new ContentChangedTriggerSchema() + ) + }; + + var errors = await RuleTriggerValidator.ValidateAsync(appName, trigger, appProvider); + + Assert.Empty(errors); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs new file mode 100644 index 000000000..aa9b3abe9 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs @@ -0,0 +1,116 @@ +// ========================================================================== +// RuleCommandMiddlewareTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using FakeItEasy; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Entities.Rules.Commands; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Entities.TestHelpers; +using Squidex.Infrastructure.Commands; +using Xunit; + +namespace Squidex.Domain.Apps.Entities.Rules +{ + public class RuleCommandMiddlewareTests : HandlerTestBase + { + private readonly IAppProvider appProvider = A.Fake(); + private readonly RuleCommandMiddleware sut; + private readonly RuleDomainObject rule; + private readonly RuleTrigger ruleTrigger = new ContentChangedTrigger(); + private readonly RuleAction ruleAction = new WebhookAction { Url = new Uri("https://squidex.io") }; + private readonly Guid ruleId = Guid.NewGuid(); + + public RuleCommandMiddlewareTests() + { + A.CallTo(() => appProvider.GetSchemaAsync(A.Ignored, A.Ignored, false)) + .Returns(A.Fake()); + + rule = new RuleDomainObject(); + + sut = new RuleCommandMiddleware(Handler, appProvider); + } + + [Fact] + public async Task Create_should_create_domain_object() + { + var context = CreateContextForCommand(new CreateRule { Trigger = ruleTrigger, Action = ruleAction }); + + await TestCreate(rule, async _ => + { + await sut.HandleAsync(context); + }); + } + + [Fact] + public async Task Update_should_update_domain_object() + { + var context = CreateContextForCommand(new UpdateRule { Trigger = ruleTrigger, Action = ruleAction }); + + CreateRule(); + + await TestUpdate(rule, async _ => + { + await sut.HandleAsync(context); + }); + } + + [Fact] + public async Task Enable_should_update_domain_object() + { + CreateRule(); + DisableRule(); + + var command = CreateContextForCommand(new EnableRule { RuleId = ruleId }); + + await TestUpdate(rule, async _ => + { + await sut.HandleAsync(command); + }); + } + + [Fact] + public async Task Disable_should_update_domain_object() + { + CreateRule(); + + var command = CreateContextForCommand(new DisableRule { RuleId = ruleId }); + + await TestUpdate(rule, async _ => + { + await sut.HandleAsync(command); + }); + } + + [Fact] + public async Task Delete_should_update_domain_object() + { + CreateRule(); + + var command = CreateContextForCommand(new DeleteRule { RuleId = ruleId }); + + await TestUpdate(rule, async _ => + { + await sut.HandleAsync(command); + }); + } + + private void DisableRule() + { + rule.Disable(new DisableRule()); + } + + private void CreateRule() + { + rule.Create(new CreateRule { Trigger = ruleTrigger, Action = ruleAction }); + } + } +} \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs new file mode 100644 index 000000000..10dd3948b --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs @@ -0,0 +1,249 @@ +// ========================================================================== +// RuleDomainObjectTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Immutable; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Entities.Rules.Commands; +using Squidex.Domain.Apps.Entities.TestHelpers; +using Squidex.Domain.Apps.Events.Rules; +using Squidex.Infrastructure; +using Xunit; + +namespace Squidex.Domain.Apps.Entities.Rules +{ + public class RuleDomainObjectTests : HandlerTestBase + { + private readonly RuleTrigger ruleTrigger = new ContentChangedTrigger(); + private readonly RuleAction ruleAction = new WebhookAction { Url = new Uri("https://squidex.io") }; + private readonly RuleDomainObject sut; + + public Guid RuleId { get; } = Guid.NewGuid(); + + public RuleDomainObjectTests() + { + sut = new RuleDomainObject(); + } + + [Fact] + public void Create_should_throw_exception_if_created() + { + sut.Create(new CreateRule { Trigger = ruleTrigger, Action = ruleAction }); + + Assert.Throws(() => + { + sut.Create(CreateRuleCommand(new CreateRule { Trigger = ruleTrigger, Action = ruleAction })); + }); + } + + [Fact] + public void Create_should_create_events() + { + var command = new CreateRule { Trigger = ruleTrigger, Action = ruleAction }; + + sut.Create(CreateRuleCommand(command)); + + Assert.Same(ruleTrigger, sut.State.RuleDef.Trigger); + Assert.Same(ruleAction, sut.State.RuleDef.Action); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateRuleEvent(new RuleCreated { Trigger = ruleTrigger, Action = ruleAction }) + ); + } + + [Fact] + public void Update_should_throw_exception_if_not_created() + { + Assert.Throws(() => + { + sut.Update(CreateRuleCommand(new UpdateRule { Trigger = ruleTrigger, Action = ruleAction })); + }); + } + + [Fact] + public void Update_should_throw_exception_if_rule_is_deleted() + { + CreateRule(); + DeleteRule(); + + Assert.Throws(() => + { + sut.Update(CreateRuleCommand(new UpdateRule { Trigger = ruleTrigger, Action = ruleAction })); + }); + } + + [Fact] + public void Update_should_create_events() + { + var newTrigger = new ContentChangedTrigger + { + Schemas = ImmutableList.Empty + }; + + var newAction = new WebhookAction + { + Url = new Uri("https://squidex.io/v2") + }; + + CreateRule(); + + var command = new UpdateRule { Trigger = newTrigger, Action = newAction }; + + sut.Update(CreateRuleCommand(command)); + + Assert.Same(newTrigger, sut.State.RuleDef.Trigger); + Assert.Same(newAction, sut.State.RuleDef.Action); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateRuleEvent(new RuleUpdated { Trigger = newTrigger, Action = newAction }) + ); + } + + [Fact] + public void Enable_should_throw_exception_if_not_created() + { + Assert.Throws(() => + { + sut.Enable(CreateRuleCommand(new EnableRule())); + }); + } + + [Fact] + public void Enable_should_throw_exception_if_rule_is_deleted() + { + CreateRule(); + DeleteRule(); + + Assert.Throws(() => + { + sut.Enable(CreateRuleCommand(new EnableRule())); + }); + } + + [Fact] + public void Enable_should_create_events() + { + CreateRule(); + + var command = new EnableRule(); + + sut.Enable(CreateRuleCommand(command)); + + Assert.True(sut.State.RuleDef.IsEnabled); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateRuleEvent(new RuleEnabled()) + ); + } + + [Fact] + public void Disable_should_throw_exception_if_not_created() + { + Assert.Throws(() => + { + sut.Disable(CreateRuleCommand(new DisableRule())); + }); + } + + [Fact] + public void Disable_should_throw_exception_if_rule_is_deleted() + { + CreateRule(); + DeleteRule(); + + Assert.Throws(() => + { + sut.Disable(CreateRuleCommand(new DisableRule())); + }); + } + + [Fact] + public void Disable_should_create_events() + { + CreateRule(); + + var command = new DisableRule(); + + sut.Disable(CreateRuleCommand(command)); + + Assert.False(sut.State.RuleDef.IsEnabled); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateRuleEvent(new RuleDisabled()) + ); + } + + [Fact] + public void Delete_should_throw_exception_if_not_created() + { + Assert.Throws(() => + { + sut.Delete(CreateRuleCommand(new DeleteRule())); + }); + } + + [Fact] + public void Delete_should_throw_exception_if_already_deleted() + { + CreateRule(); + DeleteRule(); + + Assert.Throws(() => + { + sut.Delete(CreateRuleCommand(new DeleteRule())); + }); + } + + [Fact] + public void Delete_should_update_create_events() + { + CreateRule(); + + sut.Delete(CreateRuleCommand(new DeleteRule())); + + Assert.True(sut.State.IsDeleted); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateRuleEvent(new RuleDeleted()) + ); + } + + private void CreateRule() + { + sut.Create(CreateRuleCommand(new CreateRule { Trigger = ruleTrigger, Action = ruleAction })); + sut.ClearUncommittedEvents(); + } + + private void DeleteRule() + { + sut.Delete(CreateRuleCommand(new DeleteRule())); + sut.ClearUncommittedEvents(); + } + + protected T CreateRuleEvent(T @event) where T : RuleEvent + { + @event.RuleId = RuleId; + + return CreateEvent(@event); + } + + protected T CreateRuleCommand(T command) where T : RuleAggregateCommand + { + command.RuleId = RuleId; + + return CreateCommand(command); + } + } +} \ No newline at end of file