Browse Source

App grain stuff.

pull/382/head
Sebastian Stehle 7 years ago
parent
commit
d7624866b9
  1. 9
      src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs
  2. 9
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AddWorkflow.cs
  3. 32
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs
  4. 14
      src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs
  5. 3
      src/Squidex.Domain.Apps.Events/Apps/AppWorkflowAdded.cs
  6. 2
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs
  7. 2
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs
  8. 2
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs
  9. 27
      tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsTests.cs
  10. 52
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs
  11. 4
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs
  12. 4
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppRolesTests.cs
  13. 108
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppWorkflowTests.cs

9
src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs

@ -34,11 +34,11 @@ namespace Squidex.Domain.Apps.Core.Contents
} }
[Pure] [Pure]
public Workflows Add(string name) public Workflows Add(Guid workflowId, string name)
{ {
Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(name, nameof(name));
return new Workflows(With(Guid.NewGuid(), Workflow.CreateDefault(name))); return new Workflows(With(workflowId, Workflow.CreateDefault(name)));
} }
[Pure] [Pure]
@ -54,6 +54,11 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
Guard.NotNull(workflow, nameof(workflow)); Guard.NotNull(workflow, nameof(workflow));
if (id == Guid.Empty)
{
return Set(workflow);
}
if (!ContainsKey(id)) if (!ContainsKey(id))
{ {
return this; return this;

9
src/Squidex.Domain.Apps.Entities/Apps/Commands/AddWorkflow.cs

@ -5,10 +5,19 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class AddWorkflow : AppCommand public sealed class AddWorkflow : AppCommand
{ {
public Guid WorkflowId { get; set; }
public string Name { get; set; } public string Name { get; set; }
public AddWorkflow()
{
WorkflowId = Guid.NewGuid();
}
} }
} }

32
src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppWorkflows.cs

@ -14,11 +14,26 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
public static class GuardAppWorkflows public static class GuardAppWorkflows
{ {
public static void CanAdd(AddWorkflow command)
{
Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot add workflow.", e =>
{
if (string.IsNullOrWhiteSpace(command.Name))
{
e(Not.Defined("Name"), nameof(command.Name));
}
});
}
public static void CanUpdate(Workflows workflows, UpdateWorkflow command) public static void CanUpdate(Workflows workflows, UpdateWorkflow command)
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
Validate.It(() => "Cannot configure workflow.", e => GetWorkflowOrThrow(workflows, command.WorkflowId);
Validate.It(() => "Cannot update workflow.", e =>
{ {
if (command.Workflow == null) if (command.Workflow == null)
{ {
@ -74,14 +89,21 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
}); });
} }
internal static void CanAdd(AddWorkflow c) public static void CanDelete(Workflows workflows, DeleteWorkflow command)
{ {
throw new NotImplementedException(); Guard.NotNull(command, nameof(command));
GetWorkflowOrThrow(workflows, command.WorkflowId);
} }
internal static void CanDelete(Workflows workflows, DeleteWorkflow c) private static Workflow GetWorkflowOrThrow(Workflows workflows, Guid id)
{ {
throw new NotImplementedException(); if (!workflows.TryGetValue(id, out var workflow))
{
throw new DomainObjectNotFoundException(id.ToString(), "Workflows", typeof(IAppEntity));
}
return workflow;
} }
} }
} }

14
src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs

@ -96,9 +96,19 @@ namespace Squidex.Domain.Apps.Entities.Apps.State
Clients = Clients.Revoke(@event.Id); Clients = Clients.Revoke(@event.Id);
} }
protected void On(AppWorkflowConfigured @event) protected void On(AppWorkflowAdded @event)
{ {
Workflows = Workflows.Set(@event.Workflow); Workflows = Workflows.Add(@event.WorkflowId, @event.Name);
}
protected void On(AppWorkflowUpdated @event)
{
Workflows = Workflows.Update(@event.WorkflowId, @event.Workflow);
}
protected void On(AppWorkflowDeleted @event)
{
Workflows = Workflows.Remove(@event.WorkflowId);
} }
protected void On(AppPatternAdded @event) protected void On(AppPatternAdded @event)

3
src/Squidex.Domain.Apps.Events/Apps/AppWorkflowAdded.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Apps namespace Squidex.Domain.Apps.Events.Apps
@ -12,6 +13,8 @@ namespace Squidex.Domain.Apps.Events.Apps
[EventType(nameof(AppWorkflowAdded))] [EventType(nameof(AppWorkflowAdded))]
public sealed class AppWorkflowAdded : AppEvent public sealed class AppWorkflowAdded : AppEvent
{ {
public Guid WorkflowId { get; set; }
public string Name { get; set; } public string Name { get; set; }
} }
} }

2
tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs

@ -95,7 +95,7 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
{ {
var clients_1 = clients_0.Revoke("2"); var clients_1 = clients_0.Revoke("2");
Assert.Empty(clients_1); Assert.NotEmpty(clients_1);
} }
} }
} }

2
tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs

@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
{ {
var patterns_1 = patterns_0.Remove(id); var patterns_1 = patterns_0.Remove(id);
Assert.Empty(patterns_1); Assert.NotEmpty(patterns_1);
} }
} }
} }

2
tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesTests.cs

@ -71,7 +71,7 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
{ {
var roles_1 = roles_0.Remove(role); var roles_1 = roles_0.Remove(role);
Assert.Empty(roles_1); Assert.NotEmpty(roles_1);
} }
[Fact] [Fact]

27
tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsTests.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Xunit; using Xunit;
@ -38,20 +37,32 @@ namespace Squidex.Domain.Apps.Core.Model.Contents
[Fact] [Fact]
public void Should_add_new_workflow_with_default_states() public void Should_add_new_workflow_with_default_states()
{ {
var workflows_1 = workflows_0.Add("1"); var id = Guid.NewGuid();
Assert.Equal(workflows_1.GetFirst().Steps.Keys, new[] { Status.Archived, Status.Draft, Status.Published }); var workflows_1 = workflows_0.Add(id, "1");
Assert.Equal(workflows_1[id].Steps.Keys, new[] { Status.Archived, Status.Draft, Status.Published });
} }
[Fact] [Fact]
public void Should_update_workflow() public void Should_update_workflow()
{ {
var workflows_1 = workflows_0.Add("1"); var id = Guid.NewGuid();
var workflows_2 = workflows_1.Update(workflows_1.Keys.First(), Workflow.Empty);
var workflows_1 = workflows_0.Add(id, "1");
var workflows_2 = workflows_1.Update(id, Workflow.Empty);
Assert.Empty(workflows_2.GetFirst().Steps.Keys); Assert.Empty(workflows_2.GetFirst().Steps.Keys);
} }
[Fact]
public void Should_update_workflow_with_default_guid()
{
var workflows_1 = workflows_0.Update(Guid.Empty, Workflow.Empty);
Assert.NotEmpty(workflows_1);
}
[Fact] [Fact]
public void Should_do_nothing_if_workflow_to_update_not_found() public void Should_do_nothing_if_workflow_to_update_not_found()
{ {
@ -63,8 +74,10 @@ namespace Squidex.Domain.Apps.Core.Model.Contents
[Fact] [Fact]
public void Should_remove_workflow() public void Should_remove_workflow()
{ {
var workflows_1 = workflows_0.Add("1"); var id = Guid.NewGuid();
var workflows_2 = workflows_1.Remove(workflows_1.Keys.First());
var workflows_1 = workflows_0.Add(id, "1");
var workflows_2 = workflows_1.Remove(id);
Assert.Empty(workflows_2); Assert.Empty(workflows_2);
} }

52
tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs

@ -38,6 +38,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
private readonly string planIdPaid = "premium"; private readonly string planIdPaid = "premium";
private readonly string planIdFree = "free"; private readonly string planIdFree = "free";
private readonly AppGrain sut; private readonly AppGrain sut;
private readonly Guid workflowId = Guid.NewGuid();
private readonly Guid patternId1 = Guid.NewGuid(); private readonly Guid patternId1 = Guid.NewGuid();
private readonly Guid patternId2 = Guid.NewGuid(); private readonly Guid patternId2 = Guid.NewGuid();
private readonly Guid patternId3 = Guid.NewGuid(); private readonly Guid patternId3 = Guid.NewGuid();
@ -299,9 +300,9 @@ namespace Squidex.Domain.Apps.Entities.Apps
} }
[Fact] [Fact]
public async Task ConfigureWorkflow_should_create_events_and_update_state() public async Task AddWorkflow_should_create_events_and_update_state()
{ {
var command = new UpdateWorkflow { Workflow = Workflow.Default }; var command = new AddWorkflow { WorkflowId = workflowId, Name = "my-workflow" };
await ExecuteCreateAsync(); await ExecuteCreateAsync();
@ -313,7 +314,47 @@ namespace Squidex.Domain.Apps.Entities.Apps
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateEvent(new AppWorkflowConfigured { Workflow = Workflow.Default }) CreateEvent(new AppWorkflowAdded { WorkflowId = workflowId, Name = "my-workflow" })
);
}
[Fact]
public async Task UpdateWorkflow_should_create_events_and_update_state()
{
var command = new UpdateWorkflow { WorkflowId = workflowId, Workflow = Workflow.Default };
await ExecuteCreateAsync();
await ExecuteAddWorkflowAsync();
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(sut.Snapshot);
Assert.NotEmpty(sut.Snapshot.Workflows);
LastEvents
.ShouldHaveSameEvents(
CreateEvent(new AppWorkflowUpdated { WorkflowId = workflowId, Workflow = Workflow.Default })
);
}
[Fact]
public async Task DeleteWorkflow_should_create_events_and_update_state()
{
var command = new DeleteWorkflow { WorkflowId = workflowId };
await ExecuteCreateAsync();
await ExecuteAddWorkflowAsync();
var result = await sut.ExecuteAsync(CreateCommand(command));
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Empty(sut.Snapshot.Workflows);
LastEvents
.ShouldHaveSameEvents(
CreateEvent(new AppWorkflowDeleted { WorkflowId = workflowId })
); );
} }
@ -540,6 +581,11 @@ namespace Squidex.Domain.Apps.Entities.Apps
return sut.ExecuteAsync(CreateCommand(new AddLanguage { Language = language })); return sut.ExecuteAsync(CreateCommand(new AddLanguage { Language = language }));
} }
private Task ExecuteAddWorkflowAsync()
{
return sut.ExecuteAsync(CreateCommand(new AddWorkflow { WorkflowId = workflowId, Name = "my-workflow" }));
}
private Task ExecuteChangePlanAsync() private Task ExecuteChangePlanAsync()
{ {
return sut.ExecuteAsync(CreateCommand(new ChangePlan { PlanId = planIdPaid })); return sut.ExecuteAsync(CreateCommand(new ChangePlan { PlanId = planIdPaid }));

4
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs

@ -71,7 +71,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
} }
[Fact] [Fact]
public void CanAdd_should_not_throw_exception_if_success() public void CanAdd_should_not_throw_exception_if_command_is_valid()
{ {
var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" }; var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" };
@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
} }
[Fact] [Fact]
public void CanDelete_should_not_throw_exception_if_success() public void CanDelete_should_not_throw_exception_if_command_is_valid()
{ {
var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message");

4
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppRolesTests.cs

@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
} }
[Fact] [Fact]
public void CanAdd_should_not_throw_exception_if_success() public void CanAdd_should_not_throw_exception_if_command_is_valid()
{ {
var command = new AddRole { Name = roleName }; var command = new AddRole { Name = roleName };
@ -101,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
} }
[Fact] [Fact]
public void CanDelete_should_not_throw_exception_if_success() public void CanDelete_should_not_throw_exception_if_command_is_valid()
{ {
var roles_1 = roles_0.Add(roleName); var roles_1 = roles_0.Add(roleName);

108
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppWorkflowTests.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
@ -16,17 +17,54 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
public class GuardAppWorkflowTests public class GuardAppWorkflowTests
{ {
private readonly Guid workflowId = Guid.NewGuid();
private readonly Workflows workflows;
public GuardAppWorkflowTests()
{
workflows = Workflows.Empty.Add(workflowId, "name");
}
[Fact]
public void CanAdd_should_throw_exception_if_name_is_not_defined()
{
var command = new AddWorkflow();
ValidationAssert.Throws(() => GuardAppWorkflows.CanAdd(command),
new ValidationError("Name is required.", "Name"));
}
[Fact]
public void CanAdd_should_not_throw_exception_if_command_is_valid()
{
var command = new AddWorkflow { Name = "my-workflow" };
GuardAppWorkflows.CanAdd(command);
}
[Fact]
public void CanUpdate_should_throw_exception_if_workflow_not_found()
{
var command = new UpdateWorkflow
{
Workflow = Workflow.Empty,
WorkflowId = Guid.NewGuid()
};
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanUpdate(workflows, command));
}
[Fact] [Fact]
public void CanConfigure_should_throw_exception_if_workflow_is_not_defined() public void CanUpdate_should_throw_exception_if_workflow_is_not_defined()
{ {
var command = new UpdateWorkflow(); var command = new UpdateWorkflow { WorkflowId = workflowId };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
new ValidationError("Workflow is required.", "Workflow")); new ValidationError("Workflow is required.", "Workflow"));
} }
[Fact] [Fact]
public void CanConfigure_should_throw_exception_if_workflow_has_no_initial_step() public void CanUpdate_should_throw_exception_if_workflow_has_no_initial_step()
{ {
var command = new UpdateWorkflow var command = new UpdateWorkflow
{ {
@ -35,15 +73,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
[Status.Published] = new WorkflowStep() [Status.Published] = new WorkflowStep()
}, },
default) default),
WorkflowId = workflowId
}; };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
new ValidationError("Initial step is required.", "Workflow.Initial")); new ValidationError("Initial step is required.", "Workflow.Initial"));
} }
[Fact] [Fact]
public void CanConfigure_should_throw_exception_if_initial_step_is_published() public void CanUpdate_should_throw_exception_if_initial_step_is_published()
{ {
var command = new UpdateWorkflow var command = new UpdateWorkflow
{ {
@ -52,15 +91,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
[Status.Published] = new WorkflowStep() [Status.Published] = new WorkflowStep()
}, },
Status.Published) Status.Published),
WorkflowId = workflowId
}; };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
new ValidationError("Initial step cannot be published step.", "Workflow.Initial")); new ValidationError("Initial step cannot be published step.", "Workflow.Initial"));
} }
[Fact] [Fact]
public void CanConfigure_should_throw_exception_if_workflow_does_not_have_published_state() public void CanUpdate_should_throw_exception_if_workflow_does_not_have_published_state()
{ {
var command = new UpdateWorkflow var command = new UpdateWorkflow
{ {
@ -69,15 +109,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
[Status.Draft] = new WorkflowStep() [Status.Draft] = new WorkflowStep()
}, },
Status.Draft) Status.Draft),
WorkflowId = workflowId
}; };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
new ValidationError("Workflow must have a published step.", "Workflow.Steps")); new ValidationError("Workflow must have a published step.", "Workflow.Steps"));
} }
[Fact] [Fact]
public void CanConfigure_should_throw_exception_if_workflow_step_is_not_defined() public void CanUpdate_should_throw_exception_if_workflow_step_is_not_defined()
{ {
var command = new UpdateWorkflow var command = new UpdateWorkflow
{ {
@ -87,15 +128,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
[Status.Published] = null, [Status.Published] = null,
[Status.Draft] = new WorkflowStep() [Status.Draft] = new WorkflowStep()
}, },
Status.Draft) Status.Draft),
WorkflowId = workflowId
}; };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
new ValidationError("Step is required.", "Workflow.Steps.Published")); new ValidationError("Step is required.", "Workflow.Steps.Published"));
} }
[Fact] [Fact]
public void CanConfigure_should_throw_exception_if_workflow_transition_is_invalid() public void CanUpdate_should_throw_exception_if_workflow_transition_is_invalid()
{ {
var command = new UpdateWorkflow var command = new UpdateWorkflow
{ {
@ -110,15 +152,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
}), }),
[Status.Draft] = new WorkflowStep() [Status.Draft] = new WorkflowStep()
}, },
Status.Draft) Status.Draft),
WorkflowId = workflowId
}; };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
new ValidationError("Transition has an invalid target.", "Workflow.Steps.Published.Transitions.Archived")); new ValidationError("Transition has an invalid target.", "Workflow.Steps.Published.Transitions.Archived"));
} }
[Fact] [Fact]
public void CanConfigure_should_throw_exception_if_workflow_transition_is_not_defined() public void CanUpdate_should_throw_exception_if_workflow_transition_is_not_defined()
{ {
var command = new UpdateWorkflow var command = new UpdateWorkflow
{ {
@ -134,19 +177,36 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards
[Status.Draft] = null [Status.Draft] = null
}) })
}, },
Status.Draft) Status.Draft),
WorkflowId = workflowId
}; };
ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(command), ValidationAssert.Throws(() => GuardAppWorkflows.CanUpdate(workflows, command),
new ValidationError("Transition is required.", "Workflow.Steps.Published.Transitions.Draft")); new ValidationError("Transition is required.", "Workflow.Steps.Published.Transitions.Draft"));
} }
[Fact] [Fact]
public void CanConfigure_should_not_throw_exception_if_workflow_is_valid() public void CanUpdate_should_not_throw_exception_if_workflow_is_valid()
{
var command = new UpdateWorkflow { Workflow = Workflow.Default, WorkflowId = workflowId };
GuardAppWorkflows.CanUpdate(workflows, command);
}
[Fact]
public void CanDelete_should_throw_exception_if_workflow_not_found()
{
var command = new DeleteWorkflow { WorkflowId = Guid.NewGuid() };
Assert.Throws<DomainObjectNotFoundException>(() => GuardAppWorkflows.CanDelete(workflows, command));
}
[Fact]
public void CanDelete_should_not_throw_exception_if_workflow_is_found()
{ {
var command = new UpdateWorkflow { Workflow = Workflow.Default }; var command = new DeleteWorkflow { WorkflowId = workflowId };
GuardAppWorkflows.CanUpdate(command); GuardAppWorkflows.CanDelete(workflows, command);
} }
} }
} }

Loading…
Cancel
Save