Treat content schedule dates as UTC (#19028)

This commit is contained in:
Kenn Jacobsen
2025-04-14 14:50:02 +02:00
committed by GitHub
parent f9496e8067
commit 7495c3c7b2
13 changed files with 172 additions and 25 deletions

View File

@@ -245,13 +245,13 @@ public static class ContentExtensions
}
IEnumerable<ContentSchedule> expires = contentSchedule.GetSchedule(culture, ContentScheduleAction.Expire);
if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.Now > x.Date))
if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.UtcNow > x.Date))
{
return ContentStatus.Expired;
}
IEnumerable<ContentSchedule> release = contentSchedule.GetSchedule(culture, ContentScheduleAction.Release);
if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.Now))
if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.UtcNow))
{
return ContentStatus.AwaitingRelease;
}

View File

@@ -57,7 +57,7 @@ public abstract class UmbracoIntegrationTestWithContent : UmbracoIntegrationTest
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1054
Subpage = ContentBuilder.CreateSimpleContent(ContentType, "Text Page 1", Textpage.Id);
Subpage.Key = new Guid(SubPageKey);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(Subpage, -1, contentSchedule);
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1055

View File

@@ -214,7 +214,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
ctVariant.Variations = ContentVariation.Culture;
ContentTypeService.Save(ctVariant);
var now = DateTime.Now;
var now = DateTime.UtcNow;
// 10x invariant content, half is scheduled to be published in 5 seconds, the other half is scheduled to be unpublished in 5 seconds
var invariant = new List<IContent>();
@@ -321,7 +321,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
// Act
var content = ContentService.CreateAndSave("Test", Constants.System.Root, "umbTextpage");
var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.Now.AddHours(2));
var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.UtcNow.AddHours(2));
ContentService.Save(content, Constants.Security.SuperUserId, contentSchedule);
Assert.AreEqual(1, contentSchedule.FullSchedule.Count);
@@ -676,7 +676,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
var root = ContentService.GetById(Textpage.Id);
ContentService.Publish(root!, root!.AvailableCultures.ToArray());
var content = ContentService.GetById(Subpage.Id);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.Now.AddSeconds(1));
var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.UtcNow.AddSeconds(1));
ContentService.PersistContentSchedule(content!, contentSchedule);
ContentService.Publish(content, content.AvailableCultures.ToArray());
@@ -1386,7 +1386,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
{
// Arrange
var content = ContentService.GetById(Subpage.Id); // This Content expired 5min ago
var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.Now.AddMinutes(-5));
var contentSchedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.UtcNow.AddMinutes(-5));
ContentService.Save(content, contentSchedule: contentSchedule);
var parent = ContentService.GetById(Textpage.Id);
@@ -1416,7 +1416,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
var content = ContentBuilder.CreateBasicContent(contentType);
content.SetCultureName("Hello", "en-US");
var contentSchedule = ContentScheduleCollection.CreateWithEntry("en-US", null, DateTime.Now.AddMinutes(-5));
var contentSchedule = ContentScheduleCollection.CreateWithEntry("en-US", null, DateTime.UtcNow.AddMinutes(-5));
ContentService.Save(content, contentSchedule: contentSchedule);
var published = ContentService.Publish(content, new[] { "en-US" });
@@ -1431,7 +1431,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
{
// Arrange
var content = ContentService.GetById(Subpage.Id);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddHours(2), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddHours(2), null);
ContentService.Save(content, Constants.Security.SuperUserId, contentSchedule);
var parent = ContentService.GetById(Textpage.Id);
@@ -1488,7 +1488,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
content.Properties[0].SetValue("Foo", string.Empty);
contentService.Save(content);
contentService.PersistContentSchedule(content,
ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddHours(2), null));
ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddHours(2), null));
// Act
var result = contentService.Publish(content, Array.Empty<string>(), userId: Constants.Security.SuperUserId);
@@ -1540,7 +1540,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
contentService.Publish(content, Array.Empty<string>());
contentService.PersistContentSchedule(content,
ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddHours(2), null));
ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddHours(2), null));
contentService.Save(content);
// Act
@@ -1568,7 +1568,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
var content = ContentBuilder.CreateBasicContent(contentType);
content.SetCultureName("Hello", "en-US");
var contentSchedule = ContentScheduleCollection.CreateWithEntry("en-US", DateTime.Now.AddHours(2), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry("en-US", DateTime.UtcNow.AddHours(2), null);
ContentService.Save(content, contentSchedule: contentSchedule);
var published = ContentService.Publish(content, new[] { "en-US" });

View File

@@ -230,7 +230,7 @@ public class DocumentUrlServiceTests : UmbracoIntegrationTestWithContent
// Create a subpage
var subsubpage = ContentBuilder.CreateSimpleContent(ContentType, documentName, Subpage.Id);
subsubpage.Key = Guid.Parse(documentKey);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(subsubpage, -1, contentSchedule);
if (loadDraft is false)
@@ -248,7 +248,7 @@ public class DocumentUrlServiceTests : UmbracoIntegrationTestWithContent
// Create a second root
var secondRoot = ContentBuilder.CreateSimpleContent(ContentType, "Second Root", null);
secondRoot.Key = new Guid("8E21BCD4-02CA-483D-84B0-1FC92702E198");
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(secondRoot, -1, contentSchedule);
if (loadDraft is false)
@@ -266,7 +266,7 @@ public class DocumentUrlServiceTests : UmbracoIntegrationTestWithContent
{
// Create a second root
var secondRoot = ContentBuilder.CreateSimpleContent(ContentType, "Second Root", null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(secondRoot, -1, contentSchedule);
// Create a child of second root

View File

@@ -62,7 +62,7 @@ public class DocumentUrlServiceTests_HideTopLevel_False : UmbracoIntegrationTest
// Create a subpage
var subsubpage = ContentBuilder.CreateSimpleContent(ContentType, "Sub Page 1", Subpage.Id);
subsubpage.Key = new Guid("DF49F477-12F2-4E33-8563-91A7CC1DCDBB");
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(subsubpage, -1, contentSchedule);
if (loadDraft is false)
@@ -81,7 +81,7 @@ public class DocumentUrlServiceTests_HideTopLevel_False : UmbracoIntegrationTest
// Create a second root
var secondRoot = ContentBuilder.CreateSimpleContent(ContentType, "Second Root", null);
secondRoot.Key = new Guid("8E21BCD4-02CA-483D-84B0-1FC92702E198");
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(secondRoot, -1, contentSchedule);
if (loadDraft is false)
@@ -100,7 +100,7 @@ public class DocumentUrlServiceTests_HideTopLevel_False : UmbracoIntegrationTest
{
// Create a second root
var secondRoot = ContentBuilder.CreateSimpleContent(ContentType, "Second Root", null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(secondRoot, -1, contentSchedule);
// Create a child of second root

View File

@@ -164,7 +164,7 @@ public class PublishStatusServiceTest : UmbracoIntegrationTestWithContent
{
var grandchild = ContentBuilder.CreateSimpleContent(ContentType, "Grandchild", Subpage2.Id);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(grandchild, -1, contentSchedule);
var publishResults = ContentService.PublishBranch(Textpage, PublishBranchFilter.IncludeUnpublished, ["*"]);

View File

@@ -12,7 +12,7 @@ public class PublishedUrlInfoProviderTests : PublishedUrlInfoProviderTestsBase
{
// Create a second root
var secondRoot = ContentBuilder.CreateSimpleContent(ContentType, "Second Root", null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(secondRoot, -1, contentSchedule);
// Create a child of second root

View File

@@ -19,7 +19,7 @@ public class PublishedUrlInfoProvider_hidetoplevel_false : PublishedUrlInfoProvi
{
// Create a second root
var secondRoot = ContentBuilder.CreateSimpleContent(ContentType, "Second Root", null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(secondRoot, -1, contentSchedule);
// Create a child of second root

View File

@@ -14,7 +14,7 @@ public partial class ContentPublishingServiceTests
var result = await ContentPublishingService.PublishAsync(
Textpage.Key,
MakeModel(ContentScheduleCollection.CreateWithEntry("*", DateTime.Now.AddDays(1), null)),
MakeModel(ContentScheduleCollection.CreateWithEntry("*", DateTime.UtcNow.AddDays(1), null)),
Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
@@ -25,7 +25,7 @@ public partial class ContentPublishingServiceTests
[Test]
public async Task Publish_Single_Item_Does_Not_Publish_Children_In_The_Future()
{
await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(ContentScheduleCollection.CreateWithEntry("*", DateTime.Now.AddDays(1), null)), Constants.Security.SuperUserKey);
await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(ContentScheduleCollection.CreateWithEntry("*", DateTime.UtcNow.AddDays(1), null)), Constants.Security.SuperUserKey);
VerifyIsNotPublished(Textpage.Key);
VerifyIsNotPublished(Subpage.Key);
@@ -36,7 +36,7 @@ public partial class ContentPublishingServiceTests
{
await ContentPublishingService.PublishAsync(Textpage.Key, MakeModel(_allCultures), Constants.Security.SuperUserKey);
var result = await ContentPublishingService.PublishAsync(Subpage.Key, MakeModel(ContentScheduleCollection.CreateWithEntry("*", DateTime.Now.AddDays(1), null)), Constants.Security.SuperUserKey);
var result = await ContentPublishingService.PublishAsync(Subpage.Key, MakeModel(ContentScheduleCollection.CreateWithEntry("*", DateTime.UtcNow.AddDays(1), null)), Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
Assert.AreEqual(ContentPublishingOperationStatus.Success, result.Status);

View File

@@ -968,7 +968,7 @@ public class EntityServiceTests : UmbracoIntegrationTest
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1054
_subpage = ContentBuilder.CreateSimpleContent(_contentType, "Text Page 1", _textpage.Id);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-5), null);
ContentService.Save(_subpage, -1, contentSchedule);
// Create and Save Content "Text Page 2" based on "umbTextpage" -> 1055

View File

@@ -0,0 +1,136 @@
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
using Umbraco.Extensions;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions;
public partial class ContentExtensionsTests
{
[Test]
public void GetStatus_WhenTrashed_ReturnsTrashed()
{
var contentMock = new Mock<IContent>();
contentMock.SetupGet(c => c.Trashed).Returns(true);
var result = contentMock.Object.GetStatus(new ContentScheduleCollection());
Assert.AreEqual(ContentStatus.Trashed, result);
}
[TestCase(true, ContentStatus.Published)]
[TestCase(false, ContentStatus.Unpublished)]
public void GetStatus_WithEmptySchedule_ReturnsPublishState(bool published, ContentStatus expectedStatus)
{
var contentTypeMock = new Mock<ISimpleContentType>();
contentTypeMock.SetupGet(c => c.Variations).Returns(ContentVariation.Nothing);
var mock = new Mock<IContent>();
mock.SetupGet(c => c.ContentType).Returns(contentTypeMock.Object);
mock.SetupGet(c => c.Published).Returns(published);
var result = mock.Object.GetStatus(new ContentScheduleCollection());
Assert.AreEqual(expectedStatus, result);
}
[TestCase(1)]
[TestCase(10)]
[TestCase(60)]
[TestCase(120)]
[TestCase(1000)]
public void GetStatus_WithPendingExpiry_ForInvariant_ReturnsExpired(int minutesFromExpiry)
{
var contentTypeMock = new Mock<ISimpleContentType>();
contentTypeMock.SetupGet(c => c.Variations).Returns(ContentVariation.Nothing);
var mock = new Mock<IContent>();
mock.SetupGet(c => c.ContentType).Returns(contentTypeMock.Object);
var schedule = ContentScheduleCollection.CreateWithEntry(null, DateTime.UtcNow.AddMinutes(-1 * minutesFromExpiry));
var result = mock.Object.GetStatus(schedule);
Assert.AreEqual(ContentStatus.Expired, result);
}
[TestCase(1)]
[TestCase(10)]
[TestCase(60)]
[TestCase(120)]
[TestCase(1000)]
public void GetStatus_WithPendingRelease_ForInvariant_ReturnsAwaitingRelease(int minutesUntilRelease)
{
var contentTypeMock = new Mock<ISimpleContentType>();
contentTypeMock.SetupGet(c => c.Variations).Returns(ContentVariation.Nothing);
var mock = new Mock<IContent>();
mock.SetupGet(c => c.ContentType).Returns(contentTypeMock.Object);
var schedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(minutesUntilRelease), null);
var result = mock.Object.GetStatus(schedule);
Assert.AreEqual(ContentStatus.AwaitingRelease, result);
}
[TestCase(1)]
[TestCase(10)]
[TestCase(60)]
[TestCase(120)]
[TestCase(1000)]
public void GetStatus_WithPastReleaseAndFutureExpiry_ForInvariant_ReturnsPublishedState(int minutes)
{
var contentTypeMock = new Mock<ISimpleContentType>();
contentTypeMock.SetupGet(c => c.Variations).Returns(ContentVariation.Nothing);
var mock = new Mock<IContent>();
mock.SetupGet(c => c.ContentType).Returns(contentTypeMock.Object);
mock.SetupGet(c => c.Published).Returns(true);
var schedule = ContentScheduleCollection.CreateWithEntry(DateTime.UtcNow.AddMinutes(-1 * minutes), DateTime.UtcNow.AddMinutes(minutes));
var result = mock.Object.GetStatus(schedule);
Assert.AreEqual(ContentStatus.Published, result);
}
[TestCase(1)]
[TestCase(10)]
[TestCase(60)]
[TestCase(120)]
[TestCase(1000)]
public void GetStatus_WithPendingExpiry_ForVariant_ReturnsExpired(int minutesFromExpiry)
{
var contentTypeMock = new Mock<ISimpleContentType>();
contentTypeMock.SetupGet(c => c.Variations).Returns(ContentVariation.Culture);
var mock = new Mock<IContent>();
mock.SetupGet(c => c.ContentType).Returns(contentTypeMock.Object);
var schedule = ContentScheduleCollection.CreateWithEntry("en-US", null, DateTime.UtcNow.AddMinutes(-1 * minutesFromExpiry));
var result = mock.Object.GetStatus(schedule, "en-US");
Assert.AreEqual(ContentStatus.Expired, result);
}
[TestCase(1)]
[TestCase(10)]
[TestCase(60)]
[TestCase(120)]
[TestCase(1000)]
public void GetStatus_WithPendingRelease_ForVariant_ReturnsAwaitingRelease(int minutesUntilRelease)
{
var contentTypeMock = new Mock<ISimpleContentType>();
contentTypeMock.SetupGet(c => c.Variations).Returns(ContentVariation.Culture);
var mock = new Mock<IContent>();
mock.SetupGet(c => c.ContentType).Returns(contentTypeMock.Object);
var schedule = ContentScheduleCollection.CreateWithEntry("en-US", DateTime.UtcNow.AddMinutes(minutesUntilRelease), null);
var result = mock.Object.GetStatus(schedule, "en-US");
Assert.AreEqual(ContentStatus.AwaitingRelease, result);
}
[TestCase(1)]
[TestCase(10)]
[TestCase(60)]
[TestCase(120)]
[TestCase(1000)]
public void GetStatus_WithPastReleaseAndFutureExpiry_ForVariant_ReturnsPublishedState(int minutes)
{
var contentTypeMock = new Mock<ISimpleContentType>();
contentTypeMock.SetupGet(c => c.Variations).Returns(ContentVariation.Culture);
var mock = new Mock<IContent>();
mock.SetupGet(c => c.ContentType).Returns(contentTypeMock.Object);
mock.SetupGet(c => c.Published).Returns(true);
var schedule = ContentScheduleCollection.CreateWithEntry("en-US", DateTime.UtcNow.AddMinutes(-1 * minutes), DateTime.UtcNow.AddMinutes(minutes));
var result = mock.Object.GetStatus(schedule, "en-US");
Assert.AreEqual(ContentStatus.Published, result);
}
}

View File

@@ -0,0 +1,8 @@
using NUnit.Framework;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions;
[TestFixture]
public partial class ContentExtensionsTests
{
}

View File

@@ -46,5 +46,8 @@
<Compile Update="Umbraco.Cms.Api.Management\Services\BackOfficeExternalLoginServiceTests.UnLinkLoginAsync.cs">
<DependentUpon>BackOfficeExternalLoginServiceTests.cs</DependentUpon>
</Compile>
<Compile Update="Umbraco.Core\Extensions\ContentExtensionsTests.GetStatus.cs">
<DependentUpon>ContentExtensionsTests.cs</DependentUpon>
</Compile>
</ItemGroup>
</Project>