From b0f42a2c86979555309f99d6e2ec0defdb7de0d9 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 10 May 2023 14:04:42 +0200 Subject: [PATCH] Move the built-in properties back to the delivery API media model + support property expansion for other media properties (#14224) Co-authored-by: Elitsa --- .../RequestContextOutputExpansionStrategy.cs | 31 ++++- .../DeliveryApi/ApiMediaBuilder.cs | 28 ++++- .../DeliveryApi/IOutputExpansionStrategy.cs | 4 +- .../NoopOutputExpansionStrategy.cs | 9 +- .../Models/DeliveryApi/ApiMedia.cs | 14 ++- .../Models/DeliveryApi/IApiMedia.cs | 18 ++- .../Models/DeliveryApi/ApiMediaWithCrops.cs | 8 ++ .../DeliveryApi/ContentBuilderTests.cs | 1 + .../DeliveryApi/DeliveryApiTests.cs | 6 +- .../DeliveryApi/MediaBuilderTests.cs | 22 ++-- .../MediaPickerValueConverterTests.cs | 1 + ...MediaPickerWithCropsValueConverterTests.cs | 41 +++--- .../MultiNodeTreePickerValueConverterTests.cs | 2 +- .../OutputExpansionStrategyTests.cs | 117 ++++++++++++++++++ .../PropertyValueConverterTests.cs | 3 + 15 files changed, 254 insertions(+), 51 deletions(-) diff --git a/src/Umbraco.Cms.Api.Delivery/Rendering/RequestContextOutputExpansionStrategy.cs b/src/Umbraco.Cms.Api.Delivery/Rendering/RequestContextOutputExpansionStrategy.cs index b20f6ec76a..bdb451e74e 100644 --- a/src/Umbraco.Cms.Api.Delivery/Rendering/RequestContextOutputExpansionStrategy.cs +++ b/src/Umbraco.Cms.Api.Delivery/Rendering/RequestContextOutputExpansionStrategy.cs @@ -22,14 +22,33 @@ internal sealed class RequestContextOutputExpansionStrategy : IOutputExpansionSt } public IDictionary MapElementProperties(IPublishedElement element) - => MapProperties(element.Properties); - - public IDictionary MapProperties(IEnumerable properties) - => properties.ToDictionary( + => element.Properties.ToDictionary( p => p.Alias, p => p.GetDeliveryApiValue(_state == ExpansionState.Expanding)); public IDictionary MapContentProperties(IPublishedContent content) + => content.ItemType == PublishedItemType.Content + ? MapProperties(content.Properties) + : throw new ArgumentException($"Invalid item type. This method can only be used with item type {nameof(PublishedItemType.Content)}, got: {content.ItemType}"); + + public IDictionary MapMediaProperties(IPublishedContent media, bool skipUmbracoProperties = true) + { + if (media.ItemType != PublishedItemType.Media) + { + throw new ArgumentException($"Invalid item type. This method can only be used with item type {PublishedItemType.Media}, got: {media.ItemType}"); + } + + IPublishedProperty[] properties = media + .Properties + .Where(p => skipUmbracoProperties is false || p.Alias.StartsWith("umbraco") is false) + .ToArray(); + + return properties.Any() + ? MapProperties(properties) + : new Dictionary(); + } + + private IDictionary MapProperties(IEnumerable properties) { // in the initial state, content properties should always be rendered (expanded if the requests dictates it). // this corresponds to the root level of a content item, i.e. when the initial content rendering starts. @@ -37,7 +56,7 @@ internal sealed class RequestContextOutputExpansionStrategy : IOutputExpansionSt { // update state to pending so we don't end up here the next time around _state = ExpansionState.Pending; - var rendered = content.Properties.ToDictionary( + var rendered = properties.ToDictionary( property => property.Alias, property => { @@ -63,7 +82,7 @@ internal sealed class RequestContextOutputExpansionStrategy : IOutputExpansionSt if (_state == ExpansionState.Expanding) { _state = ExpansionState.Expanded; - var rendered = content.Properties.ToDictionary( + var rendered = properties.ToDictionary( property => property.Alias, property => property.GetDeliveryApiValue(false)); _state = ExpansionState.Expanding; diff --git a/src/Umbraco.Core/DeliveryApi/ApiMediaBuilder.cs b/src/Umbraco.Core/DeliveryApi/ApiMediaBuilder.cs index 81c894a635..fa74aee7b2 100644 --- a/src/Umbraco.Core/DeliveryApi/ApiMediaBuilder.cs +++ b/src/Umbraco.Core/DeliveryApi/ApiMediaBuilder.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Core.Models.DeliveryApi; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.DeliveryApi; @@ -7,15 +8,18 @@ public sealed class ApiMediaBuilder : IApiMediaBuilder { private readonly IApiContentNameProvider _apiContentNameProvider; private readonly IApiMediaUrlProvider _apiMediaUrlProvider; + private readonly IPublishedValueFallback _publishedValueFallback; private readonly IOutputExpansionStrategyAccessor _outputExpansionStrategyAccessor; public ApiMediaBuilder( IApiContentNameProvider apiContentNameProvider, IApiMediaUrlProvider apiMediaUrlProvider, + IPublishedValueFallback publishedValueFallback, IOutputExpansionStrategyAccessor outputExpansionStrategyAccessor) { _apiContentNameProvider = apiContentNameProvider; _apiMediaUrlProvider = apiMediaUrlProvider; + _publishedValueFallback = publishedValueFallback; _outputExpansionStrategyAccessor = outputExpansionStrategyAccessor; } @@ -25,11 +29,27 @@ public sealed class ApiMediaBuilder : IApiMediaBuilder _apiContentNameProvider.GetName(media), media.ContentType.Alias, _apiMediaUrlProvider.GetUrl(media), + Extension(media), + Width(media), + Height(media), + Bytes(media), Properties(media)); - // map all media properties except the umbracoFile one, as we've already included the file URL etc. in the output - private IDictionary Properties(IPublishedContent media) => - _outputExpansionStrategyAccessor.TryGetValue(out IOutputExpansionStrategy? outputExpansionStrategy) - ? outputExpansionStrategy.MapProperties(media.Properties.Where(p => p.Alias != Constants.Conventions.Media.File)) + private string? Extension(IPublishedContent media) + => media.Value(_publishedValueFallback, Constants.Conventions.Media.Extension); + + private int? Width(IPublishedContent media) + => media.Value(_publishedValueFallback, Constants.Conventions.Media.Width); + + private int? Height(IPublishedContent media) + => media.Value(_publishedValueFallback, Constants.Conventions.Media.Height); + + private int? Bytes(IPublishedContent media) + => media.Value(_publishedValueFallback, Constants.Conventions.Media.Bytes); + + // map all media properties except the umbraco ones, as we've already included those in the output + private IDictionary Properties(IPublishedContent media) + => _outputExpansionStrategyAccessor.TryGetValue(out IOutputExpansionStrategy? outputExpansionStrategy) + ? outputExpansionStrategy.MapMediaProperties(media) : new Dictionary(); } diff --git a/src/Umbraco.Core/DeliveryApi/IOutputExpansionStrategy.cs b/src/Umbraco.Core/DeliveryApi/IOutputExpansionStrategy.cs index 56ed9cec73..97d0cf598b 100644 --- a/src/Umbraco.Core/DeliveryApi/IOutputExpansionStrategy.cs +++ b/src/Umbraco.Core/DeliveryApi/IOutputExpansionStrategy.cs @@ -6,7 +6,7 @@ public interface IOutputExpansionStrategy { IDictionary MapElementProperties(IPublishedElement element); - IDictionary MapProperties(IEnumerable properties); - IDictionary MapContentProperties(IPublishedContent content); + + IDictionary MapMediaProperties(IPublishedContent media, bool skipUmbracoProperties = true); } diff --git a/src/Umbraco.Core/DeliveryApi/NoopOutputExpansionStrategy.cs b/src/Umbraco.Core/DeliveryApi/NoopOutputExpansionStrategy.cs index 8ff223324a..8a2f297634 100644 --- a/src/Umbraco.Core/DeliveryApi/NoopOutputExpansionStrategy.cs +++ b/src/Umbraco.Core/DeliveryApi/NoopOutputExpansionStrategy.cs @@ -7,9 +7,12 @@ internal sealed class NoopOutputExpansionStrategy : IOutputExpansionStrategy public IDictionary MapElementProperties(IPublishedElement element) => MapProperties(element.Properties); - public IDictionary MapProperties(IEnumerable properties) - => properties.ToDictionary(p => p.Alias, p => p.GetDeliveryApiValue(true)); - public IDictionary MapContentProperties(IPublishedContent content) => MapProperties(content.Properties); + + public IDictionary MapMediaProperties(IPublishedContent media, bool skipUmbracoProperties = true) + => MapProperties(media.Properties.Where(p => skipUmbracoProperties is false || p.Alias.StartsWith("umbraco") is false)); + + private IDictionary MapProperties(IEnumerable properties) + => properties.ToDictionary(p => p.Alias, p => p.GetDeliveryApiValue(false)); } diff --git a/src/Umbraco.Core/Models/DeliveryApi/ApiMedia.cs b/src/Umbraco.Core/Models/DeliveryApi/ApiMedia.cs index df076f50a3..a161b69e2a 100644 --- a/src/Umbraco.Core/Models/DeliveryApi/ApiMedia.cs +++ b/src/Umbraco.Core/Models/DeliveryApi/ApiMedia.cs @@ -2,12 +2,16 @@ public sealed class ApiMedia : IApiMedia { - public ApiMedia(Guid id, string name, string mediaType, string url, IDictionary properties) + public ApiMedia(Guid id, string name, string mediaType, string url, string? extension, int? width, int? height, int? bytes, IDictionary properties) { Id = id; Name = name; MediaType = mediaType; Url = url; + Extension = extension; + Width = width; + Height = height; + Bytes = bytes; Properties = properties; } @@ -19,5 +23,13 @@ public sealed class ApiMedia : IApiMedia public string Url { get; } + public string? Extension { get; } + + public int? Width { get; } + + public int? Height { get; } + + public int? Bytes { get; } + public IDictionary Properties { get; } } diff --git a/src/Umbraco.Core/Models/DeliveryApi/IApiMedia.cs b/src/Umbraco.Core/Models/DeliveryApi/IApiMedia.cs index 6ae1575e61..f30b7dbc19 100644 --- a/src/Umbraco.Core/Models/DeliveryApi/IApiMedia.cs +++ b/src/Umbraco.Core/Models/DeliveryApi/IApiMedia.cs @@ -2,13 +2,21 @@ public interface IApiMedia { - public Guid Id { get; } + Guid Id { get; } - public string Name { get; } + string Name { get; } - public string MediaType { get; } + string MediaType { get; } - public string Url { get; } + string Url { get; } - public IDictionary Properties { get; } + string? Extension { get; } + + int? Width { get; } + + int? Height { get; } + + int? Bytes { get; } + + IDictionary Properties { get; } } diff --git a/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiMediaWithCrops.cs b/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiMediaWithCrops.cs index 6315766a1c..4aeaba3dea 100644 --- a/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiMediaWithCrops.cs +++ b/src/Umbraco.Infrastructure/Models/DeliveryApi/ApiMediaWithCrops.cs @@ -24,6 +24,14 @@ internal sealed class ApiMediaWithCrops : IApiMedia public string Url => _inner.Url; + public string? Extension => _inner.Extension; + + public int? Width => _inner.Width; + + public int? Height => _inner.Height; + + public int? Bytes => _inner.Bytes; + public IDictionary Properties => _inner.Properties; public ImageCropperValue.ImageCropperFocalPoint? FocalPoint { get; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ContentBuilderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ContentBuilderTests.cs index 75f960bb0f..750ac885e0 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ContentBuilderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/ContentBuilderTests.cs @@ -21,6 +21,7 @@ public class ContentBuilderTests : DeliveryApiTests var contentType = new Mock(); contentType.SetupGet(c => c.Alias).Returns("thePageType"); + contentType.SetupGet(c => c.ItemType).Returns(PublishedItemType.Content); var key = Guid.NewGuid(); var urlSegment = "url-segment"; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/DeliveryApiTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/DeliveryApiTests.cs index 5398e22bc3..3bb7339bc5 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/DeliveryApiTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/DeliveryApiTests.cs @@ -56,11 +56,11 @@ public class DeliveryApiTests DefaultPropertyType = SetupPublishedPropertyType(defaultPropertyValueConverter.Object, "default", "Default.Editor"); } - protected IPublishedPropertyType SetupPublishedPropertyType(IPropertyValueConverter valueConverter, string propertyTypeAlias, string editorAlias) + protected IPublishedPropertyType SetupPublishedPropertyType(IPropertyValueConverter valueConverter, string propertyTypeAlias, string editorAlias, object? dataTypeConfiguration = null) { var mockPublishedContentTypeFactory = new Mock(); mockPublishedContentTypeFactory.Setup(x => x.GetDataType(It.IsAny())) - .Returns(new PublishedDataType(123, editorAlias, new Lazy())); + .Returns(new PublishedDataType(123, editorAlias, new Lazy(() => dataTypeConfiguration))); var publishedPropType = new PublishedPropertyType( propertyTypeAlias, @@ -100,7 +100,7 @@ public class DeliveryApiTests }); content.SetupGet(c => c.ContentType).Returns(contentType); content.SetupGet(c => c.Properties).Returns(properties); - content.SetupGet(c => c.ItemType).Returns(PublishedItemType.Content); + content.SetupGet(c => c.ItemType).Returns(contentType.ItemType); } protected string DefaultUrlSegment(string name, string? culture = null) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaBuilderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaBuilderTests.cs index 80bc65d2a6..399b164783 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaBuilderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaBuilderTests.cs @@ -21,19 +21,21 @@ public class MediaBuilderTests : DeliveryApiTests { { Constants.Conventions.Media.Width, 111 }, { Constants.Conventions.Media.Height, 222 }, - { Constants.Conventions.Media.Extension, ".my-ext" } + { Constants.Conventions.Media.Extension, ".my-ext" }, + { Constants.Conventions.Media.Bytes, 333 } }); - var builder = new ApiMediaBuilder(new ApiContentNameProvider(), SetupMediaUrlProvider(), CreateOutputExpansionStrategyAccessor()); + var builder = new ApiMediaBuilder(new ApiContentNameProvider(), SetupMediaUrlProvider(), Mock.Of(), CreateOutputExpansionStrategyAccessor()); var result = builder.Build(media); Assert.NotNull(result); Assert.AreEqual("The media", result.Name); Assert.AreEqual("theMediaType", result.MediaType); Assert.AreEqual("media-url:media-url-segment", result.Url); - Assert.AreEqual(3, result.Properties.Count); - Assert.AreEqual(111, result.Properties[Constants.Conventions.Media.Width]); - Assert.AreEqual(222, result.Properties[Constants.Conventions.Media.Height]); - Assert.AreEqual(".my-ext", result.Properties[Constants.Conventions.Media.Extension]); + Assert.AreEqual(key, result.Id); + Assert.AreEqual(111, result.Width); + Assert.AreEqual(222, result.Height); + Assert.AreEqual(".my-ext", result.Extension); + Assert.AreEqual(333, result.Bytes); } [Test] @@ -46,7 +48,7 @@ public class MediaBuilderTests : DeliveryApiTests new Dictionary() ); - var builder = new ApiMediaBuilder(new ApiContentNameProvider(), SetupMediaUrlProvider(), CreateOutputExpansionStrategyAccessor()); + var builder = new ApiMediaBuilder(new ApiContentNameProvider(), SetupMediaUrlProvider(), Mock.Of(), CreateOutputExpansionStrategyAccessor()); var result = builder.Build(media); Assert.NotNull(result); Assert.IsEmpty(result.Properties); @@ -61,7 +63,7 @@ public class MediaBuilderTests : DeliveryApiTests "media-url-segment", new Dictionary { { "myProperty", 123 }, { "anotherProperty", "A value goes here" } }); - var builder = new ApiMediaBuilder(new ApiContentNameProvider(), SetupMediaUrlProvider(), CreateOutputExpansionStrategyAccessor()); + var builder = new ApiMediaBuilder(new ApiContentNameProvider(), SetupMediaUrlProvider(), Mock.Of(), CreateOutputExpansionStrategyAccessor()); var result = builder.Build(media); Assert.NotNull(result); Assert.AreEqual(2, result.Properties.Count); @@ -76,7 +78,7 @@ public class MediaBuilderTests : DeliveryApiTests var mediaType = new Mock(); mediaType.SetupGet(c => c.Alias).Returns("theMediaType"); - var mediaProperties = properties.Select(kvp => SetupProperty(kvp.Key, kvp.Value, media.Object)).ToArray(); + var mediaProperties = properties.Select(kvp => SetupProperty(kvp.Key, kvp.Value)).ToArray(); media.SetupGet(c => c.Properties).Returns(mediaProperties); media.SetupGet(c => c.UrlSegment).Returns(urlSegment); @@ -89,7 +91,7 @@ public class MediaBuilderTests : DeliveryApiTests return media.Object; } - private IPublishedProperty SetupProperty(string alias, T value, IPublishedContent media) + private IPublishedProperty SetupProperty(string alias, T value) { var propertyMock = new Mock(); propertyMock.SetupGet(p => p.Alias).Returns(alias); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaPickerValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaPickerValueConverterTests.cs index b629dccdb7..0bb8fa46e8 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaPickerValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaPickerValueConverterTests.cs @@ -98,5 +98,6 @@ public class MediaPickerValueConverterTests : PropertyValueConverterTests new ApiMediaBuilder( new ApiContentNameProvider(), new ApiMediaUrlProvider(PublishedUrlProvider), + Mock.Of(), CreateOutputExpansionStrategyAccessor())); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaPickerWithCropsValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaPickerWithCropsValueConverterTests.cs index 957075092e..23dc5bb8b3 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaPickerWithCropsValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MediaPickerWithCropsValueConverterTests.cs @@ -27,6 +27,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes new ApiMediaBuilder( new ApiContentNameProvider(), apiUrlProvider, + Mock.Of(), CreateOutputExpansionStrategyAccessor())); } @@ -34,7 +35,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes public void MediaPickerWithCropsValueConverter_InSingleMode_ConvertsValueToCollectionOfApiMedia() { var publishedPropertyType = SetupMediaPropertyType(false); - var mediaKey = SetupMedia("My media", ".jpg", 200, 400, "My alt text"); + var mediaKey = SetupMedia("My media", ".jpg", 200, 400, "My alt text", 800); var serializer = new JsonNetSerializer(); @@ -64,7 +65,15 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes Assert.AreEqual(1, result.Count()); Assert.AreEqual("My media", result.First().Name); Assert.AreEqual("my-media", result.First().Url); + Assert.AreEqual(".jpg", result.First().Extension); + Assert.AreEqual(200, result.First().Width); + Assert.AreEqual(400, result.First().Height); + Assert.AreEqual(800, result.First().Bytes); Assert.NotNull(result.First().FocalPoint); + Assert.AreEqual(".jpg", result.First().Extension); + Assert.AreEqual(200, result.First().Width); + Assert.AreEqual(400, result.First().Height); + Assert.AreEqual(800, result.First().Bytes); Assert.AreEqual(.2m, result.First().FocalPoint.Left); Assert.AreEqual(.4m, result.First().FocalPoint.Top); Assert.NotNull(result.First().Crops); @@ -78,19 +87,16 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes Assert.AreEqual(10m, result.First().Crops.First().Coordinates.Y1); Assert.AreEqual(20m, result.First().Crops.First().Coordinates.Y2); Assert.NotNull(result.First().Properties); - Assert.AreEqual(4, result.First().Properties.Count); + Assert.AreEqual(1, result.First().Properties.Count); Assert.AreEqual("My alt text", result.First().Properties["altText"]); - Assert.AreEqual(".jpg", result.First().Properties[Constants.Conventions.Media.Extension]); - Assert.AreEqual(200, result.First().Properties[Constants.Conventions.Media.Width]); - Assert.AreEqual(400, result.First().Properties[Constants.Conventions.Media.Height]); } [Test] public void MediaPickerWithCropsValueConverter_InMultiMode_ConvertsValueToMedias() { var publishedPropertyType = SetupMediaPropertyType(true); - var mediaKey1 = SetupMedia("My media", ".jpg", 200, 400, "My alt text"); - var mediaKey2 = SetupMedia("My other media", ".png", 800, 600, "My other alt text"); + var mediaKey1 = SetupMedia("My media", ".jpg", 200, 400, "My alt text", 800); + var mediaKey2 = SetupMedia("My other media", ".png", 800, 600, "My other alt text", 200); var serializer = new JsonNetSerializer(); @@ -135,6 +141,10 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes Assert.AreEqual("My media", result.First().Name); Assert.AreEqual("my-media", result.First().Url); + Assert.AreEqual(".jpg", result.First().Extension); + Assert.AreEqual(200, result.First().Width); + Assert.AreEqual(400, result.First().Height); + Assert.AreEqual(800, result.First().Bytes); Assert.NotNull(result.First().FocalPoint); Assert.AreEqual(.2m, result.First().FocalPoint.Left); Assert.AreEqual(.4m, result.First().FocalPoint.Top); @@ -149,14 +159,15 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes Assert.AreEqual(10m, result.First().Crops.First().Coordinates.Y1); Assert.AreEqual(20m, result.First().Crops.First().Coordinates.Y2); Assert.NotNull(result.First().Properties); - Assert.AreEqual(4, result.First().Properties.Count); + Assert.AreEqual(1, result.First().Properties.Count); Assert.AreEqual("My alt text", result.First().Properties["altText"]); - Assert.AreEqual(".jpg", result.First().Properties[Constants.Conventions.Media.Extension]); - Assert.AreEqual(200, result.First().Properties[Constants.Conventions.Media.Width]); - Assert.AreEqual(400, result.First().Properties[Constants.Conventions.Media.Height]); Assert.AreEqual("My other media", result.Last().Name); Assert.AreEqual("my-other-media", result.Last().Url); + Assert.AreEqual(".png", result.Last().Extension); + Assert.AreEqual(800, result.Last().Width); + Assert.AreEqual(600, result.Last().Height); + Assert.AreEqual(200, result.Last().Bytes); Assert.NotNull(result.Last().FocalPoint); Assert.AreEqual(.8m, result.Last().FocalPoint.Left); Assert.AreEqual(.6m, result.Last().FocalPoint.Top); @@ -171,11 +182,8 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes Assert.AreEqual(2m, result.Last().Crops.Last().Coordinates.Y1); Assert.AreEqual(1m, result.Last().Crops.Last().Coordinates.Y2); Assert.NotNull(result.Last().Properties); - Assert.AreEqual(4, result.Last().Properties.Count); + Assert.AreEqual(1, result.Last().Properties.Count); Assert.AreEqual("My other alt text", result.Last().Properties["altText"]); - Assert.AreEqual(".png", result.Last().Properties[Constants.Conventions.Media.Extension]); - Assert.AreEqual(800, result.Last().Properties[Constants.Conventions.Media.Width]); - Assert.AreEqual(600, result.Last().Properties[Constants.Conventions.Media.Height]); } [TestCase("")] @@ -233,7 +241,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes return publishedPropertyType.Object; } - private Guid SetupMedia(string name, string extension, int width, int height, string altText) + private Guid SetupMedia(string name, string extension, int width, int height, string altText, int bytes) { var publishedMediaType = new Mock(); publishedMediaType.SetupGet(c => c.ItemType).Returns(PublishedItemType.Media); @@ -257,6 +265,7 @@ public class MediaPickerWithCropsValueConverterTests : PropertyValueConverterTes AddProperty(Constants.Conventions.Media.Extension, extension); AddProperty(Constants.Conventions.Media.Width, width); AddProperty(Constants.Conventions.Media.Height, height); + AddProperty(Constants.Conventions.Media.Bytes, bytes); AddProperty("altText", altText); PublishedMediaCacheMock diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MultiNodeTreePickerValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MultiNodeTreePickerValueConverterTests.cs index ee4635f145..320f16dbf6 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MultiNodeTreePickerValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/MultiNodeTreePickerValueConverterTests.cs @@ -27,7 +27,7 @@ public class MultiNodeTreePickerValueConverterTests : PropertyValueConverterTest Mock.Of(), Mock.Of(), new ApiContentBuilder(contentNameProvider, routeBuilder, expansionStrategyAccessor), - new ApiMediaBuilder(contentNameProvider, apiUrProvider, expansionStrategyAccessor)); + new ApiMediaBuilder(contentNameProvider, apiUrProvider, Mock.Of(), expansionStrategyAccessor)); } private PublishedDataType MultiNodePickerPublishedDataType(bool multiSelect, string entityType) => diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/OutputExpansionStrategyTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/OutputExpansionStrategyTests.cs index 86aa8215db..fe3a9bdf71 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/OutputExpansionStrategyTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/OutputExpansionStrategyTests.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PropertyEditors.DeliveryApi; using Umbraco.Cms.Core.PropertyEditors.ValueConverters; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.Serialization; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.DeliveryApi; @@ -19,6 +20,7 @@ public class OutputExpansionStrategyTests : PropertyValueConverterTests { private IPublishedContentType _contentType; private IPublishedContentType _elementType; + private IPublishedContentType _mediaType; [SetUp] public void SetUp() @@ -31,6 +33,10 @@ public class OutputExpansionStrategyTests : PropertyValueConverterTests elementType.SetupGet(c => c.Alias).Returns("theElementType"); elementType.SetupGet(c => c.ItemType).Returns(PublishedItemType.Element); _elementType = elementType.Object; + var mediaType = new Mock(); + mediaType.SetupGet(c => c.Alias).Returns("theMediaType"); + mediaType.SetupGet(c => c.ItemType).Returns(PublishedItemType.Media); + _mediaType = mediaType.Object; } [Test] @@ -91,6 +97,47 @@ public class OutputExpansionStrategyTests : PropertyValueConverterTests Assert.AreEqual(78, contentPickerTwoOutput.Properties["numberTwo"]); } + [TestCase(false)] + [TestCase(true)] + public void OutputExpansionStrategy_CanExpandSpecificMedia(bool mediaPicker3) + { + var accessor = CreateOutputExpansionStrategyAccessor(false, new[] { "mediaPickerTwo" }); + var apiMediaBuilder = new ApiMediaBuilder( + new ApiContentNameProvider(), + new ApiMediaUrlProvider(PublishedUrlProvider), + Mock.Of(), + accessor); + + var media = new Mock(); + + var mediaPickerOneContent = CreateSimplePickedMedia(12, 34); + var mediaPickerOneProperty = mediaPicker3 + ? CreateMediaPicker3Property(media.Object, mediaPickerOneContent.Key, "mediaPickerOne", apiMediaBuilder) + : CreateMediaPickerProperty(media.Object, mediaPickerOneContent.Key, "mediaPickerOne", apiMediaBuilder); + var mediaPickerTwoContent = CreateSimplePickedMedia(56, 78); + var mediaPickerTwoProperty = mediaPicker3 + ? CreateMediaPicker3Property(media.Object, mediaPickerTwoContent.Key, "mediaPickerTwo", apiMediaBuilder) + : CreateMediaPickerProperty(media.Object, mediaPickerTwoContent.Key, "mediaPickerTwo", apiMediaBuilder); + + SetupMediaMock(media, mediaPickerOneProperty, mediaPickerTwoProperty); + + var result = apiMediaBuilder.Build(media.Object); + + Assert.AreEqual(2, result.Properties.Count); + + var mediaPickerOneOutput = (result.Properties["mediaPickerOne"] as IEnumerable)?.FirstOrDefault(); + Assert.IsNotNull(mediaPickerOneOutput); + Assert.AreEqual(mediaPickerOneContent.Key, mediaPickerOneOutput.Id); + Assert.IsEmpty(mediaPickerOneOutput.Properties); + + var mediaPickerTwoOutput = (result.Properties["mediaPickerTwo"] as IEnumerable)?.FirstOrDefault(); + Assert.IsNotNull(mediaPickerTwoOutput); + Assert.AreEqual(mediaPickerTwoContent.Key, mediaPickerTwoOutput.Id); + Assert.AreEqual(2, mediaPickerTwoOutput.Properties.Count); + Assert.AreEqual(56, mediaPickerTwoOutput.Properties["numberOne"]); + Assert.AreEqual(78, mediaPickerTwoOutput.Properties["numberTwo"]); + } + [Test] public void OutputExpansionStrategy_CanExpandAllContent() { @@ -339,6 +386,30 @@ public class OutputExpansionStrategyTests : PropertyValueConverterTests Assert.AreEqual(0, nestedContentPickerOutput.Properties.Count); } + [Test] + public void OutputExpansionStrategy_MappingContent_ThrowsOnInvalidItemType() + { + var accessor = CreateOutputExpansionStrategyAccessor(); + if (accessor.TryGetValue(out IOutputExpansionStrategy outputExpansionStrategy) is false) + { + Assert.Fail("Could not obtain the output expansion strategy"); + } + + Assert.Throws(() => outputExpansionStrategy.MapContentProperties(PublishedMedia)); + } + + [Test] + public void OutputExpansionStrategy_MappingMedia_ThrowsOnInvalidItemType() + { + var accessor = CreateOutputExpansionStrategyAccessor(); + if (accessor.TryGetValue(out IOutputExpansionStrategy outputExpansionStrategy) is false) + { + Assert.Fail("Could not obtain the output expansion strategy"); + } + + Assert.Throws(() => outputExpansionStrategy.MapMediaProperties(PublishedContent)); + } + private IOutputExpansionStrategyAccessor CreateOutputExpansionStrategyAccessor(bool expandAll = false, string[]? expandPropertyAliases = null) { var httpContextMock = new Mock(); @@ -370,6 +441,16 @@ public class OutputExpansionStrategyTests : PropertyValueConverterTests RegisterContentWithProviders(content.Object); } + private void SetupMediaMock(Mock media, params IPublishedProperty[] properties) + { + var key = Guid.NewGuid(); + var name = "The media"; + var urlSegment = "media-url-segment"; + ConfigurePublishedContentMock(media, key, name, urlSegment, _mediaType, properties); + + RegisterMediaWithProviders(media.Object); + } + private IPublishedContent CreateSimplePickedContent(int numberOneValue, int numberTwoValue) { var content = new Mock(); @@ -381,6 +462,17 @@ public class OutputExpansionStrategyTests : PropertyValueConverterTests return content.Object; } + private IPublishedContent CreateSimplePickedMedia(int numberOneValue, int numberTwoValue) + { + var media = new Mock(); + SetupMediaMock( + media, + CreateNumberProperty(media.Object, numberOneValue, "numberOne"), + CreateNumberProperty(media.Object, numberTwoValue, "numberTwo")); + + return media.Object; + } + private IPublishedContent CreateMultiLevelPickedContent(int numberValue, IPublishedContent nestedContentPickerValue, string nestedContentPickerPropertyTypeAlias, ApiContentBuilder apiContentBuilder) { var content = new Mock(); @@ -400,6 +492,31 @@ public class OutputExpansionStrategyTests : PropertyValueConverterTests return new PublishedElementPropertyBase(contentPickerPropertyType, parent, false, PropertyCacheLevel.None, new GuidUdi(Constants.UdiEntityType.Document, pickedContentKey).ToString()); } + private PublishedElementPropertyBase CreateMediaPickerProperty(IPublishedElement parent, Guid pickedMediaKey, string propertyTypeAlias, IApiMediaBuilder mediaBuilder) + { + MediaPickerValueConverter mediaPickerValueConverter = new MediaPickerValueConverter(PublishedSnapshotAccessor, Mock.Of(), mediaBuilder); + var mediaPickerPropertyType = SetupPublishedPropertyType(mediaPickerValueConverter, propertyTypeAlias, Constants.PropertyEditors.Aliases.MediaPicker, new MediaPickerConfiguration()); + + return new PublishedElementPropertyBase(mediaPickerPropertyType, parent, false, PropertyCacheLevel.None, new GuidUdi(Constants.UdiEntityType.Media, pickedMediaKey).ToString()); + } + + private PublishedElementPropertyBase CreateMediaPicker3Property(IPublishedElement parent, Guid pickedMediaKey, string propertyTypeAlias, IApiMediaBuilder mediaBuilder) + { + var serializer = new JsonNetSerializer(); + var value = serializer.Serialize(new[] + { + new MediaPicker3PropertyEditor.MediaPicker3PropertyValueEditor.MediaWithCropsDto + { + MediaKey = pickedMediaKey + } + }); + + MediaPickerWithCropsValueConverter mediaPickerValueConverter = new MediaPickerWithCropsValueConverter(PublishedSnapshotAccessor, PublishedUrlProvider, Mock.Of(), new JsonNetSerializer(), mediaBuilder); + var mediaPickerPropertyType = SetupPublishedPropertyType(mediaPickerValueConverter, propertyTypeAlias, Constants.PropertyEditors.Aliases.MediaPicker3, new MediaPicker3Configuration()); + + return new PublishedElementPropertyBase(mediaPickerPropertyType, parent, false, PropertyCacheLevel.None, value); + } + private PublishedElementPropertyBase CreateNumberProperty(IPublishedElement parent, int propertyValue, string propertyTypeAlias) { var numberPropertyType = SetupPublishedPropertyType(new IntegerValueConverter(), propertyTypeAlias, Constants.PropertyEditors.Aliases.Label); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PropertyValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PropertyValueConverterTests.cs index 8ee2cac5cf..d3831ec2e4 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PropertyValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/DeliveryApi/PropertyValueConverterTests.cs @@ -105,5 +105,8 @@ public class PropertyValueConverterTests : DeliveryApiTests PublishedMediaCacheMock .Setup(pcc => pcc.GetById(media.Key)) .Returns(media); + PublishedMediaCacheMock + .Setup(pcc => pcc.GetById(It.IsAny(), media.Key)) + .Returns(media); } }