// Copyright (c) Umbraco. // See LICENSE for more details. using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading; using Microsoft.Extensions.Logging.Abstractions; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Scoping; using IScopeProvider = Umbraco.Cms.Infrastructure.Scoping.IScopeProvider; using PropertyCollection = Umbraco.Cms.Core.Models.PropertyCollection; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Mapping; [TestFixture] public class MappingTests { [SetUp] public void MockScopeProvider() { var scopeMock = new Mock(); scopeMock.Setup(x => x.CreateScope( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Mock.Of); _scopeProvider = scopeMock.Object; } private IScopeProvider _scopeProvider; [Test] public void SimpleMap() { var definitions = new MapDefinitionCollection(() => new IMapDefinition[] { new MapperDefinition1() }); var mapper = new UmbracoMapper(definitions, _scopeProvider, NullLogger.Instance); var thing1 = new Thing1 { Value = "value" }; var thing2 = mapper.Map(thing1); Assert.IsNotNull(thing2); Assert.AreEqual("value", thing2.Value); thing2 = mapper.Map(thing1); Assert.IsNotNull(thing2); Assert.AreEqual("value", thing2.Value); thing2 = new Thing2(); mapper.Map(thing1, thing2); Assert.AreEqual("value", thing2.Value); } [Test] public void EnumerableMap() { var definitions = new MapDefinitionCollection(() => new IMapDefinition[] { new MapperDefinition1() }); var mapper = new UmbracoMapper(definitions, _scopeProvider, NullLogger.Instance); var thing1A = new Thing1 { Value = "valueA" }; var thing1B = new Thing1 { Value = "valueB" }; Thing1[] thing1 = { thing1A, thing1B }; var thing2 = mapper.Map, IEnumerable>(thing1).ToList(); Assert.IsNotNull(thing2); Assert.AreEqual(2, thing2.Count); Assert.AreEqual("valueA", thing2[0].Value); Assert.AreEqual("valueB", thing2[1].Value); thing2 = mapper.Map>(thing1).ToList(); Assert.IsNotNull(thing2); Assert.AreEqual(2, thing2.Count); Assert.AreEqual("valueA", thing2[0].Value); Assert.AreEqual("valueB", thing2[1].Value); thing2 = mapper.MapEnumerable(thing1).ToList(); Assert.IsNotNull(thing2); Assert.AreEqual(2, thing2.Count); Assert.AreEqual("valueA", thing2[0].Value); Assert.AreEqual("valueB", thing2[1].Value); } [Test] public void InheritedMap() { var definitions = new MapDefinitionCollection(() => new IMapDefinition[] { new MapperDefinition1() }); var mapper = new UmbracoMapper(definitions, _scopeProvider, NullLogger.Instance); var thing3 = new Thing3 { Value = "value" }; var thing2 = mapper.Map(thing3); Assert.IsNotNull(thing2); Assert.AreEqual("value", thing2.Value); thing2 = mapper.Map(thing3); Assert.IsNotNull(thing2); Assert.AreEqual("value", thing2.Value); thing2 = new Thing2(); mapper.Map(thing3, thing2); Assert.AreEqual("value", thing2.Value); } [Test] [Explicit] public void ConcurrentMap() { var definitions = new MapDefinitionCollection(() => new IMapDefinition[] { new MapperDefinition1(), new MapperDefinition3(), }); var mapper = new UmbracoMapper(definitions, _scopeProvider, NullLogger.Instance); // the mapper currently has a map from Thing1 to Thing2 // because Thing3 inherits from Thing1, it will map a Thing3 instance, // and register a new map from Thing3 to Thing2, // thus modifying its internal dictionaries // if timing is good, and mapper does have non-concurrent dictionaries, it fails // practically, to reproduce, one needs to add a 1s sleep in the mapper's loop // hence, this test is explicit var thing3 = new Thing3 { Value = "value" }; var thing4 = new Thing4(); Exception caught = null; void ThreadLoop() { // keep failing at mapping - and looping through the maps for (var i = 0; i < 10; i++) { try { mapper.Map(thing4); } catch (Exception e) { caught = e; Console.WriteLine($"{e.GetType().Name} {e.Message}"); } } Console.WriteLine("done"); } var thread = new Thread(ThreadLoop); thread.Start(); Thread.Sleep(1000); try { Console.WriteLine($"{DateTime.Now:O} mapping"); var thing2 = mapper.Map(thing3); Console.WriteLine($"{DateTime.Now:O} mapped"); Assert.IsNotNull(thing2); Assert.AreEqual("value", thing2.Value); } finally { thread.Join(); } } [Test] public void EnumMap() { var definitions = new MapDefinitionCollection(() => new IMapDefinition[] { new MapperDefinition4() }); var mapper = new UmbracoMapper(definitions, _scopeProvider, NullLogger.Instance); var thing5 = new Thing5 { Fruit1 = Thing5Enum.Apple, Fruit2 = Thing5Enum.Banana, Fruit3 = Thing5Enum.Cherry }; var thing6 = mapper.Map(thing5); Assert.IsNotNull(thing6); Assert.AreEqual(Thing6Enum.Apple, thing6.Fruit1); Assert.AreEqual(Thing6Enum.Banana, thing6.Fruit2); Assert.AreEqual(Thing6Enum.Cherry, thing6.Fruit3); } [Test] public void NullPropertyMap() { var definitions = new MapDefinitionCollection(() => new IMapDefinition[] { new MapperDefinition5() }); var mapper = new UmbracoMapper(definitions, _scopeProvider, NullLogger.Instance); var thing7 = new Thing7(); var thing8 = mapper.Map(thing7); Assert.IsNotNull(thing8); Assert.IsNull(thing8.Things); } private class Thing1 { public string Value { get; set; } } private class Thing3 : Thing1 { } private class Thing2 { public string Value { get; set; } } private class Thing4 { } private class Thing5 { public Thing5Enum Fruit1 { get; set; } public Thing5Enum Fruit2 { get; set; } public Thing5Enum Fruit3 { get; set; } } private enum Thing5Enum { Apple = 0, Banana = 1, Cherry = 2, } private class Thing6 { public Thing6Enum Fruit1 { get; set; } public Thing6Enum Fruit2 { get; set; } public Thing6Enum Fruit3 { get; set; } } private enum Thing6Enum { Apple = 0, Banana = 1, Cherry = 2, } private class Thing7 { public IEnumerable Things { get; set; } } private class Thing8 { public IEnumerable Things { get; set; } } private class MapperDefinition1 : IMapDefinition { public void DefineMaps(IUmbracoMapper mapper) => mapper.Define((source, context) => new Thing2(), Map); private void Map(Thing1 source, Thing2 target, MapperContext context) => target.Value = source.Value; } private class MapperDefinition3 : IMapDefinition { public void DefineMaps(IUmbracoMapper mapper) { // just some random things so that the mapper contains things mapper.Define(); mapper.Define(); mapper.Define(); mapper.Define(); mapper.Define(); } } private class MapperDefinition4 : IMapDefinition { public void DefineMaps(IUmbracoMapper mapper) { mapper.Define((source, context) => new Thing6(), Map); mapper.Define( (source, context) => (Thing6Enum)source); } private void Map(Thing5 source, Thing6 target, MapperContext context) { target.Fruit1 = context.Map(source.Fruit1); target.Fruit2 = context.Map(source.Fruit2); target.Fruit3 = context.Map(source.Fruit3); } } private class MapperDefinition5 : IMapDefinition { public void DefineMaps(IUmbracoMapper mapper) { mapper.Define((source, context) => new Thing2(), Map1); mapper.Define((source, context) => new Thing8(), Map2); } private void Map1(Thing1 source, Thing2 target, MapperContext context) => target.Value = source.Value; private void Map2(Thing7 source, Thing8 target, MapperContext context) => target.Things = context.Map>(source.Things); } }