Browse Source

Check publishing.

pull/382/head
Sebastian Stehle 7 years ago
parent
commit
af38e08e5c
  1. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentDataCommand.cs
  2. 14
      src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentUpdateCommand.cs
  3. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/PatchContent.cs
  4. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/UpdateContent.cs
  5. 16
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  6. 6
      src/Squidex.Domain.Apps.Entities/Contents/DefaultContentWorkflow.cs
  7. 15
      src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs
  8. 7
      src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs
  9. 2
      src/Squidex.Domain.Apps.Entities/Contents/IContentWorkflow.cs
  10. 8
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs
  11. 32
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs
  12. 65
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs

2
src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentDataCommand.cs

@ -12,7 +12,5 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands
public abstract class ContentDataCommand : ContentCommand
{
public NamedContentData Data { get; set; }
public bool AsDraft { get; set; }
}
}

14
src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentUpdateCommand.cs

@ -0,0 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Contents.Commands
{
public abstract class ContentUpdateCommand : ContentDataCommand
{
public bool AsDraft { get; set; }
}
}

2
src/Squidex.Domain.Apps.Entities/Contents/Commands/PatchContent.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Contents.Commands
{
public sealed class PatchContent : ContentDataCommand
public sealed class PatchContent : ContentUpdateCommand
{
}
}

2
src/Squidex.Domain.Apps.Entities/Contents/Commands/UpdateContent.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Contents.Commands
{
public sealed class UpdateContent : ContentDataCommand
public sealed class UpdateContent : ContentUpdateCommand
{
}
}

16
src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs

@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var ctx = await CreateContext(c.AppId.Id, c.SchemaId.Id, Guid.Empty, () => "Failed to create content.");
GuardContent.CanCreate(ctx.Schema, c);
await GuardContent.CanCreate(ctx.Schema, contentWorkflow, c);
await ctx.ExecuteScriptAndTransformAsync(s => s.Create, "Create", c, c.Data);
await ctx.EnrichAsync(c.Data);
@ -190,9 +190,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
}
}
private async Task<object> UpdateAsync(ContentDataCommand c, Func<NamedContentData, NamedContentData> newDataFunc, bool partial)
private async Task<object> UpdateAsync(ContentUpdateCommand command, Func<NamedContentData, NamedContentData> newDataFunc, bool partial)
{
var isProposal = c.AsDraft && Snapshot.Status == Status.Published;
var isProposal = command.AsDraft && Snapshot.Status == Status.Published;
var currentData =
isProposal ?
@ -207,22 +207,22 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (partial)
{
await ctx.ValidatePartialAsync(c.Data);
await ctx.ValidatePartialAsync(command.Data);
}
else
{
await ctx.ValidateAsync(c.Data);
await ctx.ValidateAsync(command.Data);
}
newData = await ctx.ExecuteScriptAndTransformAsync(s => s.Update, "Update", c, newData, Snapshot.Data);
newData = await ctx.ExecuteScriptAndTransformAsync(s => s.Update, "Update", command, newData, Snapshot.Data);
if (isProposal)
{
ProposeUpdate(c, newData);
ProposeUpdate(command, newData);
}
else
{
Update(c, newData);
Update(command, newData);
}
}

6
src/Squidex.Domain.Apps.Entities/Contents/DefaultContentWorkflow.cs

@ -12,6 +12,7 @@ using System.Security.Claims;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents
{
@ -54,6 +55,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
return Task.FromResult(result);
}
public Task<bool> CanPublishOnCreateAsync(ISchemaEntity schema, NamedContentData data, ClaimsPrincipal user)
{
return TaskHelper.True;
}
public Task<bool> CanMoveToAsync(IContentEntity content, Status next, ClaimsPrincipal user)
{
var result = Flow.TryGetValue(content.Status, out var step) && step.Transitions.Any(x => x.Status == next);

15
src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs

@ -43,7 +43,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
var workflow = await GetWorkflowAsync(content.AppId.Id, content.SchemaId.Id);
return workflow.TryGetTransition(content.Status, next, out var transition) && CanUse(transition, content, user);
return workflow.TryGetTransition(content.Status, next, out var transition) && CanUse(transition, content.DataDraft, user);
}
public async Task<bool> CanPublishOnCreateAsync(ISchemaEntity schema, NamedContentData data, ClaimsPrincipal user)
{
var workflow = await GetWorkflowAsync(schema.AppId.Id, schema.Id);
return workflow.TryGetTransition(workflow.Initial, Status.Published, out var transition) && CanUse(transition, data, user);
}
public async Task<bool> CanUpdateAsync(IContentEntity content)
@ -87,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
foreach (var (to, step, transition) in workflow.GetTransitions(content.Status))
{
if (CanUse(transition, content, user))
if (CanUse(transition, content.DataDraft, user))
{
result.Add(new StatusInfo(to, GetColor(step)));
}
@ -96,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return result.ToArray();
}
private bool CanUse(WorkflowTransition transition, IContentEntity content, ClaimsPrincipal user)
private bool CanUse(WorkflowTransition transition, NamedContentData data, ClaimsPrincipal user)
{
if (!string.IsNullOrWhiteSpace(transition.Role))
{
@ -108,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (!string.IsNullOrWhiteSpace(transition.Expression))
{
return scriptEngine.Evaluate("data", content.DataDraft, transition.Expression);
return scriptEngine.Evaluate("data", data, transition.Expression);
}
return true;

7
src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs

@ -16,7 +16,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{
public static class GuardContent
{
public static void CanCreate(ISchemaEntity schema, CreateContent command)
public static async Task CanCreate(ISchemaEntity schema, IContentWorkflow contentWorkflow, CreateContent command)
{
Guard.NotNull(command, nameof(command));
@ -29,6 +29,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{
throw new DomainException("Singleton content cannot be created.");
}
if (command.Publish && !await contentWorkflow.CanPublishOnCreateAsync(schema, command.Data, command.User))
{
throw new DomainException("Content workflow prevents publishing.");
}
}
public static async Task CanUpdate(IContentEntity content, IContentWorkflow contentWorkflow, UpdateContent command)

2
src/Squidex.Domain.Apps.Entities/Contents/IContentWorkflow.cs

@ -16,6 +16,8 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
Task<StatusInfo> GetInitialStatusAsync(ISchemaEntity schema);
Task<bool> CanPublishOnCreateAsync(ISchemaEntity schema, NamedContentData data, ClaimsPrincipal user);
Task<bool> CanMoveToAsync(IContentEntity content, Status next, ClaimsPrincipal user);
Task<bool> CanUpdateAsync(IContentEntity content);

8
tests/Squidex.Domain.Apps.Entities.Tests/Contents/DefaultContentWorkflowTests.cs

@ -16,6 +16,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
private readonly DefaultContentWorkflow sut = new DefaultContentWorkflow();
[Fact]
public async Task Should_always_allow_publish_on_create()
{
var result = await sut.CanPublishOnCreateAsync(null, null, null);
Assert.True(result);
}
[Fact]
public async Task Should_draft_as_initial_status()
{

32
tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs

@ -104,6 +104,36 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.Should().BeEquivalentTo(expected);
}
[Fact]
public async Task Should_allow_publish_on_create()
{
var content = CreateContent(Status.Draft, 2);
var result = await sut.CanPublishOnCreateAsync(CreateSchema(), content.DataDraft, User("Editor"));
Assert.True(result);
}
[Fact]
public async Task Should_not_allow_publish_on_create_if_data_is_invalid()
{
var content = CreateContent(Status.Draft, 4);
var result = await sut.CanPublishOnCreateAsync(CreateSchema(), content.DataDraft, User("Editor"));
Assert.False(result);
}
[Fact]
public async Task Should_not_allow_publish_on_create_if_role_not_allowed()
{
var content = CreateContent(Status.Draft, 2);
var result = await sut.CanPublishOnCreateAsync(CreateSchema(), content.DataDraft, User("Developer"));
Assert.False(result);
}
[Fact]
public async Task Should_check_is_valid_next()
{
@ -125,7 +155,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
}
[Fact]
public async Task Should_not_allow_transition_if_expression_does_not_evauate_to_true()
public async Task Should_not_allow_transition_if_data_not_valid()
{
var content = CreateContent(Status.Draft, 4);

65
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs

@ -27,51 +27,71 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
private readonly ClaimsPrincipal user = new ClaimsPrincipal();
private readonly Instant dueTimeInPast = SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromHours(1));
[Fact]
public void CanCreate_should_throw_exception_if_data_is_null()
public GuardContentTests()
{
SetupSingleton(false);
}
[Fact]
public async Task CanCreate_should_throw_exception_if_data_is_null()
{
var command = new CreateContent();
ValidationAssert.Throws(() => GuardContent.CanCreate(schema, command),
await ValidationAssert.ThrowsAsync(() => GuardContent.CanCreate(schema, contentWorkflow, command),
new ValidationError("Data is required.", "Data"));
}
[Fact]
public void CanCreate_should_throw_exception_if_singleton()
public async Task CanCreate_should_throw_exception_if_singleton()
{
SetupSingleton(true);
var command = new CreateContent { Data = new NamedContentData() };
Assert.Throws<DomainException>(() => GuardContent.CanCreate(schema, command));
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(schema, contentWorkflow, command));
}
[Fact]
public void CanCreate_should_not_throw_exception_if_singleton_and_id_is_schema_id()
public async Task CanCreate_should_not_throw_exception_if_singleton_and_id_is_schema_id()
{
SetupSingleton(true);
var command = new CreateContent { Data = new NamedContentData(), ContentId = schema.Id };
GuardContent.CanCreate(schema, command);
await GuardContent.CanCreate(schema, contentWorkflow, command);
}
[Fact]
public void CanCreate_should_not_throw_exception_if_data_is_not_null()
public async Task CanCreate_should_throw_exception_publish_not_allowed()
{
SetupSingleton(false);
SetupCanCreatePublish(false);
var command = new CreateContent { Data = new NamedContentData(), Publish = true };
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(schema, contentWorkflow, command));
}
[Fact]
public async Task CanCreate_should_not_throw_exception_publishing_allowed()
{
SetupCanCreatePublish(true);
var command = new CreateContent { Data = new NamedContentData(), Publish = true };
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(schema, contentWorkflow, command));
}
[Fact]
public async Task CanCreate_should_not_throw_exception_if_data_is_not_null()
{
var command = new CreateContent { Data = new NamedContentData() };
GuardContent.CanCreate(schema, command);
await GuardContent.CanCreate(schema, contentWorkflow, command);
}
[Fact]
public async Task CanUpdate_should_throw_exception_if_data_is_null()
{
SetupSingleton(false);
SetupCanUpdate(true);
var content = CreateContent(Status.Draft, false);
@ -84,7 +104,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanUpdate_should_throw_exception_if_workflow_blocks_it()
{
SetupSingleton(false);
SetupCanUpdate(false);
var content = CreateContent(Status.Draft, false);
@ -96,7 +115,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanUpdate_should_not_throw_exception_if_data_is_not_null()
{
SetupSingleton(false);
SetupCanUpdate(true);
var content = CreateContent(Status.Draft, false);
@ -108,7 +126,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanPatch_should_throw_exception_if_data_is_null()
{
SetupSingleton(false);
SetupCanUpdate(true);
var content = CreateContent(Status.Draft, false);
@ -121,7 +138,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanPatch_should_throw_exception_if_workflow_blocks_it()
{
SetupSingleton(false);
SetupCanUpdate(false);
var content = CreateContent(Status.Draft, false);
@ -133,7 +149,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanPatch_should_not_throw_exception_if_data_is_not_null()
{
SetupSingleton(false);
SetupCanUpdate(true);
var content = CreateContent(Status.Draft, false);
@ -145,8 +160,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanChangeStatus_should_throw_exception_if_publishing_without_pending_changes()
{
SetupSingleton(false);
var content = CreateContent(Status.Published, false);
var command = new ChangeContentStatus { Status = Status.Published };
@ -179,8 +192,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanChangeStatus_should_throw_exception_if_due_date_in_past()
{
SetupSingleton(false);
var content = CreateContent(Status.Draft, false);
var command = new ChangeContentStatus { Status = Status.Published, DueTime = dueTimeInPast, User = user };
@ -194,8 +205,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanChangeStatus_should_throw_exception_if_status_flow_not_valid()
{
SetupSingleton(false);
var content = CreateContent(Status.Draft, false);
var command = new ChangeContentStatus { Status = Status.Published, User = user };
@ -209,8 +218,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public async Task CanChangeStatus_should_not_throw_exception_if_status_flow_valid()
{
SetupSingleton(false);
var content = CreateContent(Status.Draft, false);
var command = new ChangeContentStatus { Status = Status.Published, User = user };
@ -231,8 +238,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanDiscardChanges_should_not_throw_exception_if_pending()
{
SetupSingleton(false);
var command = new DiscardChanges();
GuardContent.CanDiscardChanges(true, command);
@ -251,8 +256,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanDelete_should_not_throw_exception()
{
SetupSingleton(false);
var command = new DeleteContent();
GuardContent.CanDelete(schema, command);
@ -264,6 +267,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
.Returns(canUpdate);
}
private void SetupCanCreatePublish(bool canCreate)
{
A.CallTo(() => contentWorkflow.CanPublishOnCreateAsync(schema, A<NamedContentData>.Ignored, user))
.Returns(canCreate);
}
private void SetupSingleton(bool isSingleton)
{
A.CallTo(() => schema.SchemaDef)

Loading…
Cancel
Save