Files
Umbraco-CMS/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/DeliveryApiTests.cs
Andy Butland d623476902 Use UTC for system dates in Umbraco (#19822)
* Persist and expose Umbraco system dates as UTC (#19705)

* Updated persistence DTOs defining default dates to use UTC.

* Remove ForceToUtc = false from all persistence DTO attributes (default when not specified is true).

* Removed use of SpecifyKind setting dates to local.

* Removed unnecessary Utc suffixes on properties.

* Persist current date time with UtcNow.

* Removed further necessary Utc suffixes and fixed failing unit tests.

* Added migration for SQL server to update database date default constraints.

* Added comment justifying not providing a migration for SQLite default date constraints.

* Ensure UTC for datetimes created from persistence DTOs.

* Ensure UTC when creating dates for published content rendering in Razor and outputting in delivery API.

* Fixed migration SQL syntax.

* Introduced AuditItemFactory for creating entries for the backoffice document history, so we can control the UTC setting on the retrieved persisted dates.

* Ensured UTC dates are retrieved for document versions.

* Ensured UTC is returned for backoffice display of last edited and published for variant content.

* Fixed SQLite syntax for default current datetime.

* Apply suggestions from code review

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

* Further updates from code review.

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

* Migrate system dates from local server time to UTC (#19798)

* Add settings for the migration.

* Add migration and implement for SQL server.

* Implement for SQLite.

* Fixes from testing with SQL Server.

* Fixes from testing with SQLite.

* Code tidy.

* Cleaned up usings.

* Removed audit log date from conversion.

* Removed webhook log date from conversion.

* Updated update date initialization on saving dictionary items.

* Updated filter on log queries.

* Use timezone ID instead of system name to work cross-culture.

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
2025-08-22 11:59:23 +02:00

165 lines
8.1 KiB
C#

using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.DeliveryApi.Accessors;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.PropertyEditors.DeliveryApi;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Cms.Tests.Common;
using Umbraco.Extensions;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.DeliveryApi;
public class DeliveryApiTests
{
protected IPublishedPropertyType DeliveryApiPropertyType { get; private set; }
protected IPublishedPropertyType DefaultPropertyType { get; private set; }
protected IPublishStatusQueryService PublishStatusQueryService { get; private set; }
[SetUp]
public virtual void Setup()
{
var deliveryApiPropertyValueConverter = new Mock<IDeliveryApiPropertyValueConverter>();
deliveryApiPropertyValueConverter.Setup(p => p.ConvertIntermediateToDeliveryApiObject(
It.IsAny<IPublishedElement>(),
It.IsAny<IPublishedPropertyType>(),
It.IsAny<PropertyCacheLevel>(),
It.IsAny<object?>(),
It.IsAny<bool>(),
It.IsAny<bool>())).Returns("Delivery API value");
deliveryApiPropertyValueConverter.Setup(p => p.ConvertIntermediateToObject(
It.IsAny<IPublishedElement>(),
It.IsAny<IPublishedPropertyType>(),
It.IsAny<PropertyCacheLevel>(),
It.IsAny<object?>(),
It.IsAny<bool>())).Returns("Default value");
deliveryApiPropertyValueConverter.Setup(p => p.IsConverter(It.IsAny<IPublishedPropertyType>())).Returns(true);
deliveryApiPropertyValueConverter.Setup(p => p.IsValue(It.IsAny<object?>(), It.IsAny<PropertyValueLevel>())).Returns(true);
deliveryApiPropertyValueConverter.Setup(p => p.GetPropertyCacheLevel(It.IsAny<IPublishedPropertyType>())).Returns(PropertyCacheLevel.None);
deliveryApiPropertyValueConverter.Setup(p => p.GetDeliveryApiPropertyCacheLevel(It.IsAny<IPublishedPropertyType>())).Returns(PropertyCacheLevel.None);
deliveryApiPropertyValueConverter.Setup(p => p.GetDeliveryApiPropertyCacheLevelForExpansion(It.IsAny<IPublishedPropertyType>())).Returns(PropertyCacheLevel.None);
DeliveryApiPropertyType = SetupPublishedPropertyType(deliveryApiPropertyValueConverter.Object, "deliveryApi", "Delivery.Api.Editor");
var defaultPropertyValueConverter = new Mock<IPropertyValueConverter>();
defaultPropertyValueConverter.Setup(p => p.ConvertIntermediateToObject(
It.IsAny<IPublishedElement>(),
It.IsAny<IPublishedPropertyType>(),
It.IsAny<PropertyCacheLevel>(),
It.IsAny<object?>(),
It.IsAny<bool>())).Returns("Default value");
defaultPropertyValueConverter.Setup(p => p.IsConverter(It.IsAny<IPublishedPropertyType>())).Returns(true);
defaultPropertyValueConverter.Setup(p => p.IsValue(It.IsAny<object?>(), It.IsAny<PropertyValueLevel>())).Returns(true);
defaultPropertyValueConverter.Setup(p => p.GetPropertyCacheLevel(It.IsAny<IPublishedPropertyType>())).Returns(PropertyCacheLevel.None);
DefaultPropertyType = SetupPublishedPropertyType(defaultPropertyValueConverter.Object, "default", "Default.Editor");
var publishStatusQueryService = new Mock<IPublishStatusQueryService>();
publishStatusQueryService
.Setup(x => x.IsDocumentPublished(It.IsAny<Guid>(), It.IsAny<string>()))
.Returns(true);
publishStatusQueryService
.Setup(x => x.HasPublishedAncestorPath(It.IsAny<Guid>()))
.Returns(true);
PublishStatusQueryService = publishStatusQueryService.Object;
}
protected IPublishedPropertyType SetupPublishedPropertyType(
IPropertyValueConverter valueConverter,
string propertyTypeAlias,
string editorAlias,
object? dataTypeConfiguration = null,
ContentVariation contentVariation = ContentVariation.Nothing)
{
var mockPublishedContentTypeFactory = new Mock<IPublishedContentTypeFactory>();
mockPublishedContentTypeFactory.Setup(x => x.GetDataType(It.IsAny<int>()))
.Returns(new PublishedDataType(123, editorAlias, editorAlias, new Lazy<object>(() => dataTypeConfiguration)));
var publishedPropType = new PublishedPropertyType(
propertyTypeAlias,
123,
true,
contentVariation,
new PropertyValueConverterCollection(() => new[] { valueConverter }),
Mock.Of<IPublishedModelFactory>(),
mockPublishedContentTypeFactory.Object);
return publishedPropType;
}
protected IOutputExpansionStrategyAccessor CreateOutputExpansionStrategyAccessor() => new NoopOutputExpansionStrategyAccessor();
protected IVariationContextAccessor CreateVariationContextAccessor() => new TestVariationContextAccessor();
protected IOptions<GlobalSettings> CreateGlobalSettings(bool hideTopLevelNodeFromPath = true)
{
var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = hideTopLevelNodeFromPath };
var globalSettingsOptionsMock = new Mock<IOptions<GlobalSettings>>();
globalSettingsOptionsMock.SetupGet(s => s.Value).Returns(globalSettings);
return globalSettingsOptionsMock.Object;
}
protected void ConfigurePublishedContentMock(Mock<IPublishedContent> content, Guid key, string name, string urlSegment, IPublishedContentType contentType, IEnumerable<IPublishedProperty> properties)
{
content.SetupGet(c => c.Key).Returns(key);
content.SetupGet(c => c.Name).Returns(name);
content.SetupGet(c => c.UrlSegment).Returns(urlSegment);
content
.SetupGet(m => m.Cultures)
.Returns(new Dictionary<string, PublishedCultureInfo>()
{
{
string.Empty,
new PublishedCultureInfo(string.Empty, name, urlSegment, DateTime.UtcNow)
}
});
content.SetupGet(c => c.ContentType).Returns(contentType);
content.SetupGet(c => c.Properties).Returns(properties);
content.SetupGet(c => c.ItemType).Returns(contentType.ItemType);
content.SetupGet(c => c.Level).Returns(1);
content.Setup(c => c.IsPublished(It.IsAny<string?>())).Returns(true);
}
protected string DefaultUrlSegment(string name, string? culture = null)
=> $"{name.ToLowerInvariant().Replace(" ", "-")}{(culture.IsNullOrWhiteSpace() ? string.Empty : $"-{culture}")}";
protected virtual ApiContentRouteBuilder CreateContentRouteBuilder(
IApiContentPathProvider contentPathProvider,
IOptions<GlobalSettings> globalSettings,
IVariationContextAccessor? variationContextAccessor = null,
IRequestPreviewService? requestPreviewService = null,
IOptionsMonitor<RequestHandlerSettings>? requestHandlerSettingsMonitor = null,
IPublishedContentCache? contentCache = null,
IDocumentNavigationQueryService? navigationQueryService = null,
IPublishStatusQueryService? publishStatusQueryService = null,
IDocumentUrlService? documentUrlService = null)
{
if (requestHandlerSettingsMonitor == null)
{
var mock = new Mock<IOptionsMonitor<RequestHandlerSettings>>();
mock.SetupGet(m => m.CurrentValue).Returns(new RequestHandlerSettings());
requestHandlerSettingsMonitor = mock.Object;
}
return new ApiContentRouteBuilder(
contentPathProvider,
globalSettings,
variationContextAccessor ?? Mock.Of<IVariationContextAccessor>(),
requestPreviewService ?? Mock.Of<IRequestPreviewService>(),
requestHandlerSettingsMonitor,
contentCache ?? Mock.Of<IPublishedContentCache>(),
navigationQueryService ?? Mock.Of<IDocumentNavigationQueryService>(),
publishStatusQueryService ?? PublishStatusQueryService,
documentUrlService ?? Mock.Of<IDocumentUrlService>());
}
}