|
|
|
@ -25,88 +25,86 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
{ |
|
|
|
public class GuardContentTests : IClassFixture<TranslationsFixture> |
|
|
|
{ |
|
|
|
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>(); |
|
|
|
private readonly IContentWorkflow workflow = A.Fake<IContentWorkflow>(); |
|
|
|
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>(); |
|
|
|
private readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app"); |
|
|
|
private readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema"); |
|
|
|
private readonly ISchemaEntity schema; |
|
|
|
private readonly ISchemaEntity singleton; |
|
|
|
private readonly ClaimsPrincipal user = Mocks.FrontendUser(); |
|
|
|
private readonly Instant dueTimeInPast = SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromHours(1)); |
|
|
|
private readonly RefToken actor = RefToken.User("123"); |
|
|
|
|
|
|
|
public GuardContentTests() |
|
|
|
{ |
|
|
|
schema = |
|
|
|
Mocks.Schema(appId, schemaId, new Schema(schemaId.Name)); |
|
|
|
|
|
|
|
singleton = |
|
|
|
Mocks.Schema(appId, schemaId, new Schema(schemaId.Name, isSingleton: true)); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanCreate_should_throw_exception_if_data_is_null() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(false); |
|
|
|
|
|
|
|
var command = new CreateContent(); |
|
|
|
|
|
|
|
await ValidationAssert.ThrowsAsync(() => GuardContent.CanCreate(command, contentWorkflow, schema), |
|
|
|
await ValidationAssert.ThrowsAsync(() => GuardContent.CanCreate(command, workflow, schema), |
|
|
|
new ValidationError("Data is required.", "Data")); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanCreate_should_throw_exception_if_singleton() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(true); |
|
|
|
|
|
|
|
var command = new CreateContent { Data = new ContentData() }; |
|
|
|
|
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(command, contentWorkflow, schema)); |
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(command, workflow, singleton)); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanCreate_should_not_throw_exception_if_singleton_and_id_is_schema_id() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(true); |
|
|
|
|
|
|
|
var command = new CreateContent { Data = new ContentData(), ContentId = schema.Id }; |
|
|
|
|
|
|
|
await GuardContent.CanCreate(command, contentWorkflow, schema); |
|
|
|
await GuardContent.CanCreate(command, workflow, schema); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanCreate_should_throw_exception_publish_not_allowed() |
|
|
|
public async Task CanCreate_should_throw_exception_if_publishing_not_allowed() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(false); |
|
|
|
|
|
|
|
SetupCanCreatePublish(schema, false); |
|
|
|
SetupCanCreatePublish(false); |
|
|
|
|
|
|
|
var command = new CreateContent { Data = new ContentData(), Publish = true }; |
|
|
|
|
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(command, contentWorkflow, schema)); |
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(command, workflow, schema)); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanCreate_should_not_throw_exception_publishing_allowed() |
|
|
|
public async Task CanCreate_should_not_throw_exception_if_publishing_allowed() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(false); |
|
|
|
|
|
|
|
SetupCanCreatePublish(schema, true); |
|
|
|
SetupCanCreatePublish(true); |
|
|
|
|
|
|
|
var command = new CreateContent { Data = new ContentData(), Publish = true }; |
|
|
|
|
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(command, contentWorkflow, schema)); |
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanCreate(command, workflow, schema)); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanCreate_should_not_throw_exception_if_data_is_not_null() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(false); |
|
|
|
|
|
|
|
var command = new CreateContent { Data = new ContentData() }; |
|
|
|
|
|
|
|
await GuardContent.CanCreate(command, contentWorkflow, schema); |
|
|
|
await GuardContent.CanCreate(command, workflow, schema); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanUpdate_should_throw_exception_if_data_is_null() |
|
|
|
{ |
|
|
|
SetupCanUpdate(true); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Draft); |
|
|
|
|
|
|
|
var command = CreateCommand(new UpdateContent()); |
|
|
|
|
|
|
|
await ValidationAssert.ThrowsAsync(() => GuardContent.CanUpdate(command, content, contentWorkflow), |
|
|
|
await ValidationAssert.ThrowsAsync(() => GuardContent.CanUpdate(command, content, workflow), |
|
|
|
new ValidationError("Data is required.", "Data")); |
|
|
|
} |
|
|
|
|
|
|
|
@ -116,9 +114,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
SetupCanUpdate(false); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Draft); |
|
|
|
|
|
|
|
var command = CreateCommand(new UpdateContent { Data = new ContentData() }); |
|
|
|
|
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanUpdate(command, content, contentWorkflow)); |
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanUpdate(command, content, workflow)); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
@ -127,9 +126,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
SetupCanUpdate(true); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Draft); |
|
|
|
|
|
|
|
var command = CreateCommand(new UpdateContent { Data = new ContentData() }); |
|
|
|
|
|
|
|
await GuardContent.CanUpdate(command, content, contentWorkflow); |
|
|
|
await GuardContent.CanUpdate(command, content, workflow); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
@ -138,9 +138,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
SetupCanUpdate(true); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Draft); |
|
|
|
|
|
|
|
var command = CreateCommand(new PatchContent()); |
|
|
|
|
|
|
|
await ValidationAssert.ThrowsAsync(() => GuardContent.CanPatch(command, content, contentWorkflow), |
|
|
|
await ValidationAssert.ThrowsAsync(() => GuardContent.CanPatch(command, content, workflow), |
|
|
|
new ValidationError("Data is required.", "Data")); |
|
|
|
} |
|
|
|
|
|
|
|
@ -150,9 +151,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
SetupCanUpdate(false); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Draft); |
|
|
|
|
|
|
|
var command = CreateCommand(new PatchContent { Data = new ContentData() }); |
|
|
|
|
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanPatch(command, content, contentWorkflow)); |
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanPatch(command, content, workflow)); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
@ -161,95 +163,91 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
SetupCanUpdate(true); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Draft); |
|
|
|
|
|
|
|
var command = CreateCommand(new PatchContent { Data = new ContentData() }); |
|
|
|
|
|
|
|
await GuardContent.CanPatch(command, content, contentWorkflow); |
|
|
|
await GuardContent.CanPatch(command, content, workflow); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanChangeStatus_should_throw_exception_if_singleton() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(true); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Published); |
|
|
|
|
|
|
|
var command = CreateCommand(new ChangeContentStatus { Status = Status.Draft }); |
|
|
|
|
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema)); |
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanChangeStatus(command, content, workflow, contentRepository, singleton)); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanChangeStatus_should_throw_exception_if_due_date_in_past() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(false); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Draft); |
|
|
|
|
|
|
|
var command = CreateCommand(new ChangeContentStatus { Status = Status.Published, DueTime = dueTimeInPast }); |
|
|
|
|
|
|
|
A.CallTo(() => contentWorkflow.CanMoveToAsync(content, content.Status, command.Status, user)) |
|
|
|
A.CallTo(() => workflow.CanMoveToAsync(content, content.Status, command.Status, user)) |
|
|
|
.Returns(true); |
|
|
|
|
|
|
|
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema), |
|
|
|
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(command, content, workflow, contentRepository, schema), |
|
|
|
new ValidationError("Due time must be in the future.", "DueTime")); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanChangeStatus_should_throw_exception_if_status_flow_not_valid() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(false); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Draft); |
|
|
|
|
|
|
|
var command = CreateCommand(new ChangeContentStatus { Status = Status.Published }); |
|
|
|
|
|
|
|
A.CallTo(() => contentWorkflow.CanMoveToAsync(content, content.Status, command.Status, user)) |
|
|
|
A.CallTo(() => workflow.CanMoveToAsync(content, content.Status, command.Status, user)) |
|
|
|
.Returns(false); |
|
|
|
|
|
|
|
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema), |
|
|
|
await ValidationAssert.ThrowsAsync(() => GuardContent.CanChangeStatus(command, content, workflow, contentRepository, schema), |
|
|
|
new ValidationError("Cannot change status from Draft to Published.", "Status")); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanChangeStatus_should_throw_exception_if_referenced() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(true); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Published); |
|
|
|
|
|
|
|
var command = CreateCommand(new ChangeContentStatus { Status = Status.Draft }); |
|
|
|
|
|
|
|
A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, content.Id, SearchScope.Published)) |
|
|
|
.Returns(true); |
|
|
|
|
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema)); |
|
|
|
await Assert.ThrowsAsync<ValidationException>(() => GuardContent.CanChangeStatus(command, content, workflow, contentRepository, schema)); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanChangeStatus_should_not_throw_exception_if_singleton_is_published() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(true); |
|
|
|
|
|
|
|
var content = CreateDraftContent(Status.Draft); |
|
|
|
|
|
|
|
var command = CreateCommand(new ChangeContentStatus { Status = Status.Published }); |
|
|
|
|
|
|
|
await GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema); |
|
|
|
await GuardContent.CanChangeStatus(command, content, workflow, contentRepository, singleton); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanChangeStatus_should_not_throw_exception_if_status_flow_valid() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(false); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Draft); |
|
|
|
|
|
|
|
var command = CreateCommand(new ChangeContentStatus { Status = Status.Published }); |
|
|
|
|
|
|
|
A.CallTo(() => contentWorkflow.CanMoveToAsync(content, content.Status, command.Status, user)) |
|
|
|
A.CallTo(() => workflow.CanMoveToAsync(content, content.Status, command.Status, user)) |
|
|
|
.Returns(true); |
|
|
|
|
|
|
|
await GuardContent.CanChangeStatus(command, content, contentWorkflow, contentRepository, schema); |
|
|
|
await GuardContent.CanChangeStatus(command, content, workflow, contentRepository, schema); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void CreateDraft_should_throw_exception_if_not_published() |
|
|
|
{ |
|
|
|
var content = CreateContent(Status.Draft); |
|
|
|
|
|
|
|
var command = CreateCommand(new CreateContentDraft()); |
|
|
|
|
|
|
|
Assert.Throws<DomainException>(() => GuardContent.CanCreateDraft(command, content)); |
|
|
|
@ -259,6 +257,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
public void CreateDraft_should_not_throw_exception() |
|
|
|
{ |
|
|
|
var content = CreateContent(Status.Published); |
|
|
|
|
|
|
|
var command = CreateCommand(new CreateContentDraft()); |
|
|
|
|
|
|
|
GuardContent.CanCreateDraft(command, content); |
|
|
|
@ -267,9 +266,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
[Fact] |
|
|
|
public void CanDeleteDraft_should_throw_exception_if_no_draft_found() |
|
|
|
{ |
|
|
|
CreateSchema(false); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Published); |
|
|
|
|
|
|
|
var command = CreateCommand(new DeleteContentDraft()); |
|
|
|
|
|
|
|
Assert.Throws<DomainException>(() => GuardContent.CanDeleteDraft(command, content)); |
|
|
|
@ -279,6 +277,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
public void CanDeleteDraft_should_not_throw_exception() |
|
|
|
{ |
|
|
|
var content = CreateDraftContent(Status.Draft); |
|
|
|
|
|
|
|
var command = CreateCommand(new DeleteContentDraft()); |
|
|
|
|
|
|
|
GuardContent.CanDeleteDraft(command, content); |
|
|
|
@ -287,21 +286,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
[Fact] |
|
|
|
public async Task CanDelete_should_throw_exception_if_singleton() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(true); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Published); |
|
|
|
|
|
|
|
var command = CreateCommand(new DeleteContent()); |
|
|
|
|
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanDelete(command, content, contentRepository, schema)); |
|
|
|
await Assert.ThrowsAsync<DomainException>(() => GuardContent.CanDelete(command, content, contentRepository, singleton)); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public async Task CanDelete_should_throw_exception_if_referenced() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(true); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Published); |
|
|
|
var command = CreateCommand(new DeleteContent()); |
|
|
|
|
|
|
|
var command = CreateCommand(new DeleteContent { CheckReferrers = true }); |
|
|
|
|
|
|
|
A.CallTo(() => contentRepository.HasReferrersAsync(appId.Id, content.Id, SearchScope.All)) |
|
|
|
.Returns(true); |
|
|
|
@ -312,9 +309,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
[Fact] |
|
|
|
public async Task CanDelete_should_not_throw_exception() |
|
|
|
{ |
|
|
|
var schema = CreateSchema(false); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Published); |
|
|
|
|
|
|
|
var command = CreateCommand(new DeleteContent()); |
|
|
|
|
|
|
|
await GuardContent.CanDelete(command, content, contentRepository, schema); |
|
|
|
@ -324,21 +320,35 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
public void CheckPermission_should_not_throw_exception_if_content_is_from_current_user() |
|
|
|
{ |
|
|
|
var content = CreateContent(status: Status.Published); |
|
|
|
|
|
|
|
var command = CreateCommand(new DeleteContent()); |
|
|
|
|
|
|
|
GuardContent.CheckPermission(content, command, Permissions.AppContentsDelete); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void CheckPermission_should_not_throw_exception_if_content_is_from_another_user_but_user_has_permission() |
|
|
|
public void CheckPermission_should_not_throw_exception_if_user_is_null() |
|
|
|
{ |
|
|
|
var permission = Permissions.ForApp(Permissions.AppContentsDelete, appId.Name, schemaId.Name).Id; |
|
|
|
var content = CreateContent(Status.Published); |
|
|
|
|
|
|
|
var otherUser = Mocks.FrontendUser(permission: permission); |
|
|
|
var otherActor = RefToken.User("456"); |
|
|
|
var commandActor = RefToken.User("456"); |
|
|
|
var command = CreateCommand(new DeleteContent { Actor = commandActor }); |
|
|
|
|
|
|
|
command.User = null; |
|
|
|
|
|
|
|
GuardContent.CheckPermission(content, command, Permissions.AppContentsDelete); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void CheckPermission_should_not_throw_exception_if_content_is_from_another_user_but_user_has_permission() |
|
|
|
{ |
|
|
|
var content = CreateContent(Status.Published); |
|
|
|
var command = CreateCommand(new DeleteContent { Actor = otherActor, User = otherUser }); |
|
|
|
|
|
|
|
var permission = Permissions.ForApp(Permissions.AppContentsDelete, appId.Name, schemaId.Name).Id; |
|
|
|
|
|
|
|
var commandUser = Mocks.FrontendUser(permission: permission); |
|
|
|
var commandActor = RefToken.User("456"); |
|
|
|
var command = CreateCommand(new DeleteContent { Actor = commandActor, User = commandUser }); |
|
|
|
|
|
|
|
GuardContent.CheckPermission(content, command, Permissions.AppContentsDelete); |
|
|
|
} |
|
|
|
@ -346,31 +356,26 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject.Guards |
|
|
|
[Fact] |
|
|
|
public void CheckPermission_should_exception_if_content_is_from_another_user_and_user_has_no_permission() |
|
|
|
{ |
|
|
|
var otherActor = RefToken.User("456"); |
|
|
|
|
|
|
|
var content = CreateContent(Status.Published); |
|
|
|
var command = CreateCommand(new DeleteContent { Actor = otherActor }); |
|
|
|
|
|
|
|
var commandActor = RefToken.User("456"); |
|
|
|
var command = CreateCommand(new DeleteContent { Actor = commandActor }); |
|
|
|
|
|
|
|
Assert.Throws<DomainForbiddenException>(() => GuardContent.CheckPermission(content, command, Permissions.AppContentsDelete)); |
|
|
|
} |
|
|
|
|
|
|
|
private void SetupCanUpdate(bool canUpdate) |
|
|
|
{ |
|
|
|
A.CallTo(() => contentWorkflow.CanUpdateAsync(A<IContentEntity>._, A<Status>._, user)) |
|
|
|
A.CallTo(() => workflow.CanUpdateAsync(A<IContentEntity>._, A<Status>._, user)) |
|
|
|
.Returns(canUpdate); |
|
|
|
} |
|
|
|
|
|
|
|
private void SetupCanCreatePublish(ISchemaEntity schema, bool canCreate) |
|
|
|
private void SetupCanCreatePublish(bool canCreate) |
|
|
|
{ |
|
|
|
A.CallTo(() => contentWorkflow.CanPublishOnCreateAsync(schema, A<ContentData>._, user)) |
|
|
|
A.CallTo(() => workflow.CanPublishOnCreateAsync(schema, A<ContentData>._, user)) |
|
|
|
.Returns(canCreate); |
|
|
|
} |
|
|
|
|
|
|
|
private ISchemaEntity CreateSchema(bool isSingleton) |
|
|
|
{ |
|
|
|
return Mocks.Schema(appId, schemaId, new Schema(schemaId.Name, isSingleton: isSingleton)); |
|
|
|
} |
|
|
|
|
|
|
|
private T CreateCommand<T>(T command) where T : ContentCommand |
|
|
|
{ |
|
|
|
if (command.Actor == null) |
|
|
|
|