* Move AuditService to core project * Move two factor login service to core * Move ServerRegistrationService to core * Move BasicAuthService to Core project * Move IdKeyMap to core project * Added CacheInstructionService to the infrastructure namespace * Move DataTypeService to core namespace * Update CacheInstructionService.cs to use CoreScopeProvider * Move core editors to core * Move more Property editors and configuration * Remove obsoleted constructors in internal classes * Update PropertyEditors to use new ctors * Fix propertyEditors to use new ctors * Use the right property editor constructors * add DI in the property method * Update grid to use new ctor * Fix non-assignment of variable * Apply suggestions from code review Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Fix suggestions from code review Co-authored-by: Nikolaj Geisle <niko737@edu.ucl.dk> Co-authored-by: Kevin Jump <kevin@thejumps.co.uk> Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
286 lines
12 KiB
C#
286 lines
12 KiB
C#
// Copyright (c) Umbraco.
|
|
// See LICENSE for more details.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Moq;
|
|
using NUnit.Framework;
|
|
using Umbraco.Cms.Core.IO;
|
|
using Umbraco.Cms.Core.Logging;
|
|
using Umbraco.Cms.Core.Models;
|
|
using Umbraco.Cms.Core.Models.PublishedContent;
|
|
using Umbraco.Cms.Core.PropertyEditors;
|
|
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
|
using Umbraco.Cms.Core.PublishedCache;
|
|
using Umbraco.Cms.Core.PublishedCache.Internal;
|
|
using Umbraco.Cms.Core.Services;
|
|
using Umbraco.Cms.Core.Strings;
|
|
using Umbraco.Cms.Infrastructure.Serialization;
|
|
using Umbraco.Cms.Tests.Common.TestHelpers;
|
|
using Umbraco.Extensions;
|
|
|
|
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Published
|
|
{
|
|
[TestFixture]
|
|
public class NestedContentTests
|
|
{
|
|
private (IPublishedContentType, IPublishedContentType) CreateContentTypes()
|
|
{
|
|
ILogger<ProfilingLogger> logger = Mock.Of<ILogger<ProfilingLogger>>();
|
|
NullLoggerFactory loggerFactory = NullLoggerFactory.Instance;
|
|
IProfiler profiler = Mock.Of<IProfiler>();
|
|
var proflog = new ProfilingLogger(logger, profiler);
|
|
ILocalizationService localizationService = Mock.Of<ILocalizationService>();
|
|
|
|
PropertyEditorCollection editors = null;
|
|
var editor = new NestedContentPropertyEditor(Mock.Of<IDataValueEditorFactory>(),Mock.Of<IIOHelper>(), Mock.Of<IEditorConfigurationParser>());
|
|
editors = new PropertyEditorCollection(new DataEditorCollection(() => new DataEditor[] { editor }));
|
|
|
|
var serializer = new ConfigurationEditorJsonSerializer();
|
|
|
|
var dataType1 = new DataType(editor, serializer)
|
|
{
|
|
Id = 1,
|
|
Configuration = new NestedContentConfiguration
|
|
{
|
|
MinItems = 1,
|
|
MaxItems = 1,
|
|
ContentTypes = new[]
|
|
{
|
|
new NestedContentConfiguration.ContentType { Alias = "contentN1" }
|
|
}
|
|
}
|
|
};
|
|
|
|
var dataType2 = new DataType(editor, serializer)
|
|
{
|
|
Id = 2,
|
|
Configuration = new NestedContentConfiguration
|
|
{
|
|
MinItems = 1,
|
|
MaxItems = 99,
|
|
ContentTypes = new[]
|
|
{
|
|
new NestedContentConfiguration.ContentType { Alias = "contentN1" }
|
|
}
|
|
}
|
|
};
|
|
|
|
var dataType3 = new DataType(new TextboxPropertyEditor(Mock.Of<IDataValueEditorFactory>(),Mock.Of<IIOHelper>(), Mock.Of<IEditorConfigurationParser>()), serializer)
|
|
{
|
|
Id = 3
|
|
};
|
|
|
|
// mocked dataservice returns nested content preValues
|
|
var dataTypeServiceMock = new Mock<IDataTypeService>();
|
|
dataTypeServiceMock.Setup(x => x.GetAll()).Returns(new[] { dataType1, dataType2, dataType3 });
|
|
|
|
var publishedModelFactory = new Mock<IPublishedModelFactory>();
|
|
|
|
// mocked model factory returns model type
|
|
var modelTypes = new Dictionary<string, Type>
|
|
{
|
|
{ "contentN1", typeof(TestElementModel) }
|
|
};
|
|
publishedModelFactory
|
|
.Setup(x => x.MapModelType(It.IsAny<Type>()))
|
|
.Returns((Type type) => ModelType.Map(type, modelTypes));
|
|
|
|
// mocked model factory creates models
|
|
publishedModelFactory
|
|
.Setup(x => x.CreateModel(It.IsAny<IPublishedElement>()))
|
|
.Returns((IPublishedElement element) =>
|
|
{
|
|
if (element.ContentType.Alias.InvariantEquals("contentN1"))
|
|
{
|
|
return new TestElementModel(element, Mock.Of<IPublishedValueFallback>());
|
|
}
|
|
|
|
return element;
|
|
});
|
|
|
|
// mocked model factory creates model lists
|
|
publishedModelFactory
|
|
.Setup(x => x.CreateModelList(It.IsAny<string>()))
|
|
.Returns((string alias) =>
|
|
alias == "contentN1"
|
|
? (IList)new List<TestElementModel>()
|
|
: (IList)new List<IPublishedElement>());
|
|
|
|
var contentCache = new Mock<IPublishedContentCache>();
|
|
var publishedSnapshot = new Mock<IPublishedSnapshot>();
|
|
|
|
// mocked published snapshot returns a content cache
|
|
publishedSnapshot
|
|
.Setup(x => x.Content)
|
|
.Returns(contentCache.Object);
|
|
|
|
var publishedSnapshotAccessor = new Mock<IPublishedSnapshotAccessor>();
|
|
|
|
// mocked published snapshot accessor returns a facade
|
|
var localPublishedSnapshot = publishedSnapshot.Object;
|
|
publishedSnapshotAccessor
|
|
.Setup(x => x.TryGetPublishedSnapshot(out localPublishedSnapshot))
|
|
.Returns(true);
|
|
|
|
var converters = new PropertyValueConverterCollection(() => new IPropertyValueConverter[]
|
|
{
|
|
new NestedContentSingleValueConverter(publishedSnapshotAccessor.Object, publishedModelFactory.Object, proflog),
|
|
new NestedContentManyValueConverter(publishedSnapshotAccessor.Object, publishedModelFactory.Object, proflog),
|
|
});
|
|
|
|
var factory = new PublishedContentTypeFactory(publishedModelFactory.Object, converters, dataTypeServiceMock.Object);
|
|
|
|
IEnumerable<IPublishedPropertyType> CreatePropertyTypes1(IPublishedContentType contentType)
|
|
{
|
|
yield return factory.CreatePropertyType(contentType, "property1", 1);
|
|
}
|
|
|
|
IEnumerable<IPublishedPropertyType> CreatePropertyTypes2(IPublishedContentType contentType)
|
|
{
|
|
yield return factory.CreatePropertyType(contentType, "property2", 2);
|
|
}
|
|
|
|
IEnumerable<IPublishedPropertyType> CreatePropertyTypesN1(IPublishedContentType contentType)
|
|
{
|
|
yield return factory.CreatePropertyType(contentType, "propertyN1", 3);
|
|
}
|
|
|
|
IPublishedContentType contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "content1", CreatePropertyTypes1);
|
|
IPublishedContentType contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "content2", CreatePropertyTypes2);
|
|
IPublishedContentType contentTypeN1 = factory.CreateContentType(Guid.NewGuid(), 2, "contentN1", CreatePropertyTypesN1, isElement: true);
|
|
|
|
// mocked content cache returns content types
|
|
contentCache
|
|
.Setup(x => x.GetContentType(It.IsAny<string>()))
|
|
.Returns((string alias) =>
|
|
{
|
|
if (alias.InvariantEquals("contentN1"))
|
|
{
|
|
return contentTypeN1;
|
|
}
|
|
|
|
return null;
|
|
});
|
|
|
|
return (contentType1, contentType2);
|
|
}
|
|
|
|
[Test]
|
|
public void SingleNestedTest()
|
|
{
|
|
(IPublishedContentType contentType1, _) = CreateContentTypes();
|
|
|
|
// nested single converter returns the proper value clr type TestModel, and cache level
|
|
Assert.AreEqual(typeof(TestElementModel), contentType1.GetPropertyType("property1").ClrType);
|
|
Assert.AreEqual(PropertyCacheLevel.Element, contentType1.GetPropertyType("property1").CacheLevel);
|
|
|
|
var key = Guid.NewGuid();
|
|
var keyA = Guid.NewGuid();
|
|
var content = new InternalPublishedContent(contentType1)
|
|
{
|
|
Key = key,
|
|
Properties = new[]
|
|
{
|
|
new TestPublishedProperty(
|
|
contentType1.GetPropertyType("property1"), $@"[
|
|
{{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }}
|
|
]")
|
|
}
|
|
};
|
|
var value = content.Value(Mock.Of<IPublishedValueFallback>(), "property1");
|
|
|
|
// nested single converter returns proper TestModel value
|
|
Assert.IsInstanceOf<TestElementModel>(value);
|
|
var valueM = (TestElementModel)value;
|
|
Assert.AreEqual("foo", valueM.PropValue);
|
|
Assert.AreEqual(keyA, valueM.Key);
|
|
}
|
|
|
|
[Test]
|
|
public void ManyNestedTest()
|
|
{
|
|
(_, IPublishedContentType contentType2) = CreateContentTypes();
|
|
|
|
// nested many converter returns the proper value clr type IEnumerable<TestModel>, and cache level
|
|
Assert.AreEqual(typeof(IEnumerable<TestElementModel>), contentType2.GetPropertyType("property2").ClrType);
|
|
Assert.AreEqual(PropertyCacheLevel.Element, contentType2.GetPropertyType("property2").CacheLevel);
|
|
|
|
var key = Guid.NewGuid();
|
|
var keyA = Guid.NewGuid();
|
|
var keyB = Guid.NewGuid();
|
|
var content = new InternalPublishedContent(contentType2)
|
|
{
|
|
Key = key,
|
|
Properties = new[]
|
|
{
|
|
new TestPublishedProperty(contentType2.GetPropertyType("property2"), $@"[
|
|
{{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }},
|
|
{{ ""key"": ""{keyB}"", ""propertyN1"": ""bar"", ""ncContentTypeAlias"": ""contentN1"" }}
|
|
]")
|
|
}
|
|
};
|
|
var value = content.Value(Mock.Of<IPublishedValueFallback>(), "property2");
|
|
|
|
// nested many converter returns proper IEnumerable<TestModel> value
|
|
Assert.IsInstanceOf<IEnumerable<IPublishedElement>>(value);
|
|
Assert.IsInstanceOf<IEnumerable<TestElementModel>>(value);
|
|
var valueM = ((IEnumerable<TestElementModel>)value).ToArray();
|
|
Assert.AreEqual("foo", valueM[0].PropValue);
|
|
Assert.AreEqual(keyA, valueM[0].Key);
|
|
Assert.AreEqual("bar", valueM[1].PropValue);
|
|
Assert.AreEqual(keyB, valueM[1].Key);
|
|
}
|
|
|
|
public class TestElementModel : PublishedElementModel
|
|
{
|
|
public TestElementModel(IPublishedElement content, IPublishedValueFallback fallback)
|
|
: base(content, fallback)
|
|
{
|
|
}
|
|
|
|
public string PropValue => this.Value<string>(Mock.Of<IPublishedValueFallback>(), "propertyN1");
|
|
}
|
|
|
|
public class TestPublishedProperty : PublishedPropertyBase
|
|
{
|
|
private readonly bool _preview;
|
|
private readonly object _sourceValue;
|
|
private readonly bool _hasValue;
|
|
private IPublishedElement _owner;
|
|
|
|
public TestPublishedProperty(IPublishedPropertyType propertyType, object source)
|
|
: base(propertyType, PropertyCacheLevel.Element) // initial reference cache level always is .Content
|
|
{
|
|
_sourceValue = source;
|
|
_hasValue = source != null && (!(source is string ssource) || !string.IsNullOrWhiteSpace(ssource));
|
|
}
|
|
|
|
public TestPublishedProperty(IPublishedPropertyType propertyType, IPublishedElement element, bool preview, PropertyCacheLevel referenceCacheLevel, object source)
|
|
: base(propertyType, referenceCacheLevel)
|
|
{
|
|
_sourceValue = source;
|
|
_hasValue = source != null && (!(source is string ssource) || !string.IsNullOrWhiteSpace(ssource));
|
|
_owner = element;
|
|
_preview = preview;
|
|
}
|
|
|
|
private object InterValue => PropertyType.ConvertSourceToInter(null, _sourceValue, false);
|
|
|
|
internal void SetOwner(IPublishedElement owner) => _owner = owner;
|
|
|
|
public override bool HasValue(string culture = null, string? segment = null) => _hasValue;
|
|
|
|
public override object GetSourceValue(string culture = null, string? segment = null) => _sourceValue;
|
|
|
|
public override object GetValue(string culture = null, string? segment = null) => PropertyType.ConvertInterToObject(_owner, ReferenceCacheLevel, InterValue, _preview);
|
|
|
|
public override object GetXPathValue(string culture = null, string? segment = null) => throw new InvalidOperationException("This method won't be implemented.");
|
|
}
|
|
}
|
|
}
|