diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs index b98ff890a..f35cb2286 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs @@ -71,15 +71,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents public NamedId SchemaId { get; set; } [BsonIgnoreIfNull] - [BsonElement("st")] + [BsonElement("sdt")] public Status? ScheduledTo { get; set; } [BsonIgnoreIfNull] - [BsonElement("sa")] + [BsonElement("sda")] public Instant? ScheduledAt { get; set; } [BsonIgnoreIfNull] - [BsonElement("sb")] + [BsonElement("sdb")] public RefToken ScheduledBy { get; set; } [BsonRequired] diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs index 5ca260cc4..e855a9aff 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs @@ -14,6 +14,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands { public Status Status { get; set; } - public Instant? DueDate { get; set; } + public Instant? DueTime { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs index 73f4619f1..7674f1512 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs @@ -109,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { GuardContent.CanChangeContentStatus(content.Snapshot.Status, command); - if (!command.DueDate.HasValue) + if (!command.DueTime.HasValue) { var operationContext = await CreateContext(command, content, () => "Failed to patch content."); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs index e27a947a4..3cda651fb 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs @@ -46,9 +46,9 @@ namespace Squidex.Domain.Apps.Entities.Contents { VerifyCreatedAndNotDeleted(); - if (command.DueDate.HasValue) + if (command.DueTime.HasValue) { - RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled())); + RaiseEvent(SimpleMapper.Map(command, new ContentStatusScheduled { DueTime = command.DueTime.Value })); } else { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs index 00cd6526a..dfd5d8b68 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs @@ -65,9 +65,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards error(new ValidationError($"Content cannot be changed from status {status} to {command.Status}.", nameof(command.Status))); } - if (command.DueDate.HasValue && command.DueDate.Value < SystemClock.Instance.GetCurrentInstant()) + if (command.DueTime.HasValue && command.DueTime.Value < SystemClock.Instance.GetCurrentInstant()) { - error(new ValidationError("DueDate must be in the future.", nameof(command.DueDate))); + error(new ValidationError("DueTime must be in the future.", nameof(command.DueTime))); } }); } diff --git a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs index 812bdc9cd..fc81dbc70 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs @@ -215,11 +215,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/publish/")] [ApiCosts(1)] - public async Task PublishContent(string name, Guid id, string dueDate = null) + public async Task PublishContent(string name, Guid id, string dueTime = null) { await contentQuery.FindSchemaAsync(App, name); - var command = CreateCommand(id, Status.Published, dueDate); + var command = CreateCommand(id, Status.Published, dueTime); await CommandBus.PublishAsync(command); @@ -230,11 +230,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/unpublish/")] [ApiCosts(1)] - public async Task UnpublishContent(string name, Guid id, string dueDate = null) + public async Task UnpublishContent(string name, Guid id, string dueTime = null) { await contentQuery.FindSchemaAsync(App, name); - var command = CreateCommand(id, Status.Draft, dueDate); + var command = CreateCommand(id, Status.Draft, dueTime); await CommandBus.PublishAsync(command); @@ -245,11 +245,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/archive/")] [ApiCosts(1)] - public async Task ArchiveContent(string name, Guid id, string dueDate = null) + public async Task ArchiveContent(string name, Guid id, string dueTime = null) { await contentQuery.FindSchemaAsync(App, name); - var command = CreateCommand(id, Status.Archived, dueDate); + var command = CreateCommand(id, Status.Archived, dueTime); await CommandBus.PublishAsync(command); @@ -260,11 +260,11 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/restore/")] [ApiCosts(1)] - public async Task RestoreContent(string name, Guid id, string dueDate = null) + public async Task RestoreContent(string name, Guid id, string dueTime = null) { await contentQuery.FindSchemaAsync(App, name); - var command = CreateCommand(id, Status.Draft, dueDate); + var command = CreateCommand(id, Status.Draft, dueTime); await CommandBus.PublishAsync(command); @@ -286,13 +286,13 @@ namespace Squidex.Areas.Api.Controllers.Contents return NoContent(); } - private static ChangeContentStatus CreateCommand(Guid id, Status status, string dueDate) + private static ChangeContentStatus CreateCommand(Guid id, Status status, string dueTime) { Instant? dt = null; - if (string.IsNullOrWhiteSpace(dueDate)) + if (string.IsNullOrWhiteSpace(dueTime)) { - var parseResult = InstantPattern.General.Parse(dueDate); + var parseResult = InstantPattern.General.Parse(dueTime); if (!parseResult.Success) { @@ -300,7 +300,7 @@ namespace Squidex.Areas.Api.Controllers.Contents } } - return new ChangeContentStatus { Status = status, ContentId = id, DueDate = dt }; + return new ChangeContentStatus { Status = status, ContentId = id, DueTime = dt }; } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs index 0d9f45594..113b574da 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs @@ -9,6 +9,7 @@ using System; using System.Security.Claims; using System.Threading.Tasks; using FakeItEasy; +using NodaTime; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; @@ -225,6 +226,21 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => scriptEngine.Execute(A.Ignored, "")).MustHaveHappened(); } + [Fact] + public async Task ChangeStatus_should_not_invoke_scripts_when_scheduled() + { + CreateContent(); + + var context = CreateContextForCommand(new ChangeContentStatus { ContentId = contentId, User = user, Status = Status.Published, DueTime = Instant.MaxValue }); + + await TestUpdate(content, async _ => + { + await sut.HandleAsync(context); + }); + + A.CallTo(() => scriptEngine.Execute(A.Ignored, "")).MustNotHaveHappened(); + } + [Fact] public async Task Delete_should_update_domain_object() { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs index b0a061422..59e7e0213 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs @@ -8,6 +8,7 @@ using System; using FakeItEasy; using FluentAssertions; +using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; @@ -207,6 +208,25 @@ namespace Squidex.Domain.Apps.Entities.Contents ); } + [Fact] + public void ChangeStatus_should_refresh_properties_and_create_scheduled_events_when_command_has_due_time() + { + CreateContent(); + + var dueTime = Instant.MaxValue; + + sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus { Status = Status.Published, DueTime = dueTime })); + + Assert.Equal(Status.Draft, sut.Snapshot.Status); + Assert.Equal(Status.Published, sut.Snapshot.ScheduledTo); + Assert.Equal(dueTime, sut.Snapshot.ScheduledAt); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateContentEvent(new ContentStatusScheduled { Status = Status.Published, DueTime = dueTime }) + ); + } + [Fact] public void Delete_should_throw_exception_if_not_created() { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs index 60dcbbeef..587a62955 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Guards; @@ -15,6 +16,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard { public class GuardContentTests { + private readonly Instant dueTimeInPast = SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromHours(1)); + [Fact] public void CanCreate_should_throw_exception_if_data_is_null() { @@ -79,6 +82,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard Assert.Throws(() => GuardContent.CanChangeContentStatus(Status.Archived, command)); } + [Fact] + public void CanChangeContentStatus_should_throw_exception_if_due_date_in_past() + { + var command = new ChangeContentStatus { Status = Status.Published, DueTime = dueTimeInPast }; + + Assert.Throws(() => GuardContent.CanChangeContentStatus(Status.Draft, command)); + } + [Fact] public void CanChangeContentStatus_not_should_throw_exception_if_status_flow_valid() {