From bb0931f8a64916a295d290493beacb341877b4e3 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 26 Jun 2019 16:28:01 +0200 Subject: [PATCH] Finalized workflow class. --- .../Contents/Json/WorkflowConverter.cs | 37 ++++++ .../Contents/Status.cs | 2 + .../Contents/StatusConverter.cs | 36 ++++++ .../Contents/Workflow.cs | 34 +---- .../Contents/Workflows.cs | 4 +- .../Apps/State/AppState.cs | 2 +- .../Contents/DynamicContentWorkflow.cs | 4 +- .../Config/Domain/SerializationServices.cs | 3 +- tests/RunCoverage.ps1 | 2 +- .../Model/Contents/StatusTests.cs | 19 +++ .../Model/Contents/WorkflowJsonTests.cs | 36 ++++++ .../Model/Contents/WorkflowTests.cs | 116 ++++++++++++++++++ .../TestUtils.cs | 3 +- 13 files changed, 261 insertions(+), 37 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowConverter.cs create mode 100644 src/Squidex.Domain.Apps.Core.Model/Contents/StatusConverter.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowJsonTests.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowTests.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowConverter.cs new file mode 100644 index 000000000..84e8092ee --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowConverter.cs @@ -0,0 +1,37 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Squidex.Infrastructure.Json.Newtonsoft; + +namespace Squidex.Domain.Apps.Core.Contents.Json +{ + public sealed class WorkflowConverter : JsonClassConverter + { + protected override void WriteValue(JsonWriter writer, Workflows value, JsonSerializer serializer) + { + var json = new Dictionary(value.Count); + + foreach (var workflow in value) + { + json.Add(workflow.Key, workflow.Value); + } + + serializer.Serialize(writer, json); + } + + protected override Workflows ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) + { + var json = serializer.Deserialize>(reader); + + return new Workflows(json.ToArray()); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs index cc204e759..32026fc44 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs @@ -6,9 +6,11 @@ // ========================================================================== using System; +using System.ComponentModel; namespace Squidex.Domain.Apps.Core.Contents { + [TypeConverter(typeof(StatusConverter))] public struct Status : IEquatable { public static readonly Status Archived = new Status("Archived"); diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/StatusConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/StatusConverter.cs new file mode 100644 index 000000000..a7ba559c7 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/StatusConverter.cs @@ -0,0 +1,36 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.ComponentModel; +using System.Globalization; + +namespace Squidex.Domain.Apps.Core.Contents +{ + public sealed class StatusConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return destinationType == typeof(string); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + return new Status(value?.ToString()); + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + return value.ToString(); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/Workflow.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/Workflow.cs index 3817b4545..5b31999a7 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/Workflow.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/Workflow.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Collections.Generic; using Squidex.Infrastructure; @@ -54,29 +53,6 @@ namespace Squidex.Domain.Apps.Core.Contents Initial = initial; } - public static Workflow Create(IReadOnlyDictionary steps, Status initial) - { - Guard.NotNull(steps, nameof(steps)); - - foreach (var step in steps.Values) - { - foreach (var transition in step.Transitions) - { - if (steps.ContainsKey(transition.Key)) - { - throw new ArgumentException("Transitions ends to an unknown step.", nameof(initial)); - } - } - } - - if (steps.ContainsKey(initial)) - { - throw new ArgumentException("Initial step not known.", nameof(initial)); - } - - return new Workflow(steps, initial); - } - public IEnumerable<(Status Status, WorkflowStep Step, WorkflowTransition Transition)> GetTransitions(Status status) { if (TryGetStep(status, out var step)) @@ -88,14 +64,16 @@ namespace Squidex.Domain.Apps.Core.Contents } } - public WorkflowTransition GetTransition(Status from, Status to) + public bool TryGetTransition(Status from, Status to, out WorkflowTransition transition) { - if (TryGetStep(from, out var step) && step.Transitions.TryGetValue(to, out var transition)) + if (TryGetStep(from, out var step) && step.Transitions.TryGetValue(to, out transition)) { - return transition; + return true; } - return null; + transition = null; + + return false; } public bool TryGetStep(Status status, out WorkflowStep step) diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs index 840e4b88c..1a461c67d 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs @@ -13,7 +13,7 @@ using Squidex.Infrastructure.Collections; namespace Squidex.Domain.Apps.Core.Contents { - public sealed class Workflows : ArrayDictionary + public sealed class Workflows : ArrayDictionary { public static readonly Workflows Empty = new Workflows(); @@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Contents { } - public Workflows(KeyValuePair[] items) + public Workflows(KeyValuePair[] items) : base(items) { } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs index 575d82a66..f9d9b3519 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs @@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.State public LanguagesConfig LanguagesConfig { get; set; } = LanguagesConfig.English; [DataMember] - public Workflows Workdlows { get; set; } = Workflows.Empty; + public Workflows Workflows { get; set; } = Workflows.Empty; [DataMember] public bool IsArchived { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs b/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs index ac23cd7ea..021545248 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs @@ -43,9 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var workflow = await GetWorkflowAsync(content.AppId.Id); - var transition = workflow.GetTransition(content.Status, next); - - return transition != null && CanUse(transition, content, user); + return workflow.TryGetTransition(content.Status, next, out var transition) && CanUse(transition, content, user); } public async Task CanUpdateAsync(IContentEntity content) diff --git a/src/Squidex/Config/Domain/SerializationServices.cs b/src/Squidex/Config/Domain/SerializationServices.cs index 44aadc738..9217ff2fb 100644 --- a/src/Squidex/Config/Domain/SerializationServices.cs +++ b/src/Squidex/Config/Domain/SerializationServices.cs @@ -46,7 +46,8 @@ namespace Squidex.Config.Domain new RuleConverter(), new SchemaConverter(), new StatusConverter(), - new StringEnumConverter()); + new StringEnumConverter(), + new WorkflowConverter()); settings.NullValueHandling = NullValueHandling.Ignore; diff --git a/tests/RunCoverage.ps1 b/tests/RunCoverage.ps1 index 01a95a5e0..77bce8d8a 100644 --- a/tests/RunCoverage.ps1 +++ b/tests/RunCoverage.ps1 @@ -76,6 +76,6 @@ if ($all -Or $web) { -oldStyle } -&"$folderHome\.nuget\packages\ReportGenerator\4.0.13.1\tools\net47\ReportGenerator.exe" ` +&"$folderHome\.nuget\packages\ReportGenerator\4.1.6\tools\net47\ReportGenerator.exe" ` -reports:"$folderWorking\$folderReports\*.xml" ` -targetdir:"$folderWorking\$folderReports\Output" \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/StatusTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/StatusTests.cs index 8cdac0bc8..1f7f48ade 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/StatusTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/StatusTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.ComponentModel; using Squidex.Domain.Apps.Core.Contents; using Xunit; @@ -12,6 +13,24 @@ namespace Squidex.Domain.Apps.Core.Model.Contents { public class StatusTests { + private readonly TypeConverter typeConverter = TypeDescriptor.GetConverter(typeof(Status)); + + [Fact] + public void Should_convert_from_string() + { + var result = typeConverter.ConvertFromString("Draft"); + + Assert.Equal(Status.Draft, result); + } + + [Fact] + public void Should_convert_to_string() + { + var result = typeConverter.ConvertToString(Status.Draft); + + Assert.Equal("Draft", result); + } + [Fact] public void Should_initialize_default() { diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowJsonTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowJsonTests.cs new file mode 100644 index 000000000..bbc5af4e9 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowJsonTests.cs @@ -0,0 +1,36 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using FluentAssertions; +using Squidex.Domain.Apps.Core.Contents; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Model.Contents +{ + public class WorkflowJsonTests + { + [Fact] + public void Should_serialize_and_deserialize() + { + var workflow = Workflow.Default; + + var serialized = workflow.SerializeAndDeserialize(); + + serialized.Should().BeEquivalentTo(workflow); + } + + [Fact] + public void Should_serialize_and_deserialize_workflows() + { + var workflow = Workflows.Empty.Set(Workflow.Default); + + var serialized = workflow.SerializeAndDeserialize(); + + serialized.Should().BeEquivalentTo(workflow); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowTests.cs new file mode 100644 index 000000000..2ccdf1c3f --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowTests.cs @@ -0,0 +1,116 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using Squidex.Domain.Apps.Core.Contents; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Model.Contents +{ + public class WorkflowTests + { + private readonly Workflow workflow = new Workflow( + new Dictionary + { + [Status.Draft] = + new WorkflowStep( + new Dictionary + { + [Status.Archived] = new WorkflowTransition("ToArchivedExpr", "ToArchivedRole"), + [Status.Published] = new WorkflowTransition("ToPublishedExpr", "ToPublishedRole") + }, + StatusColors.Draft), + [Status.Archived] = + new WorkflowStep( + new Dictionary(), + StatusColors.Archived, true), + [Status.Published] = + new WorkflowStep( + new Dictionary(), + StatusColors.Archived) + }, Status.Draft); + + [Fact] + public void Should_provide_initial_state() + { + var (status, step) = workflow.GetInitialStep(); + + Assert.Equal(Status.Draft, status); + Assert.Equal(StatusColors.Draft, step.Color); + Assert.Same(workflow.Steps[Status.Draft], step); + } + + [Fact] + public void Should_provide_step() + { + var found = workflow.TryGetStep(Status.Draft, out var step); + + Assert.True(found); + Assert.Same(workflow.Steps[Status.Draft], step); + } + + [Fact] + public void Should_not_provide_unknown_step() + { + var found = workflow.TryGetStep(default, out var step); + + Assert.False(found); + Assert.Null(step); + } + + [Fact] + public void Should_provide_transition() + { + var found = workflow.TryGetTransition(Status.Draft, Status.Archived, out var transition); + + Assert.True(found); + Assert.Equal("ToArchivedExpr", transition.Expression); + Assert.Equal("ToArchivedRole", transition.Role); + } + + [Fact] + public void Should_not_provide_transition_from_unknown_step() + { + var found = workflow.TryGetTransition(default, Status.Archived, out var transition); + + Assert.False(found); + Assert.Null(transition); + } + + [Fact] + public void Should_not_provide_transition_to_unknown_step() + { + var found = workflow.TryGetTransition(Status.Draft, default, out var transition); + + Assert.False(found); + Assert.Null(transition); + } + + [Fact] + public void Should_provide_transitions() + { + var transitions = workflow.GetTransitions(Status.Draft).ToArray(); + + Assert.Equal(2, transitions.Length); + + var (status1, step1, transition1) = transitions[0]; + + Assert.Equal(Status.Archived, status1); + Assert.Equal("ToArchivedExpr", transition1.Expression); + Assert.Equal("ToArchivedRole", transition1.Role); + Assert.Same(workflow.Steps[Status.Archived], step1); + + var (status2, step2, transition2) = transitions[1]; + + Assert.Equal(Status.Published, status2); + Assert.Equal("ToPublishedExpr", transition2.Expression); + Assert.Equal("ToPublishedRole", transition2.Role); + Assert.Same(workflow.Steps[Status.Published], step2); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs b/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs index 58692226c..e6e9b36d5 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs @@ -58,7 +58,8 @@ namespace Squidex.Domain.Apps.Core new RuleConverter(), new SchemaConverter(), new StatusConverter(), - new StringEnumConverter()), + new StringEnumConverter(), + new WorkflowConverter()), TypeNameHandling = typeNameHandling };