// Copyright (c) Umbraco. // See LICENSE for more details. using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Tests.UnitTests.TestHelpers; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Composing { [TestFixture] public class CollectionBuildersTests { private IUmbracoBuilder _composition; [SetUp] public void Setup() { IServiceCollection register = TestHelper.GetServiceCollection(); _composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); } [TearDown] public void TearDown() { } [Test] public void ContainsTypes() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); Assert.IsFalse(builder.Has()); //// Assert.IsFalse(col.ContainsType()); // does not compile IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } [Test] public void CanClearBuilderBeforeCollectionIsCreated() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); builder.Clear(); Assert.IsFalse(builder.Has()); Assert.IsFalse(builder.Has()); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col); } [Test] public void CannotClearBuilderOnceCollectionIsCreated() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); Assert.Throws(() => builder.Clear()); } [Test] public void CanAppendToBuilder() { TestCollectionBuilder builder = _composition.WithCollectionBuilder(); builder.Append(); builder.Append(); Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); Assert.IsFalse(builder.Has()); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } [Test] public void CannotAppendToBuilderOnceCollectionIsCreated() { TestCollectionBuilder builder = _composition.WithCollectionBuilder(); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); Assert.Throws(() => builder.Append()); } [Test] public void CanAppendDuplicateToBuilderAndDeDuplicate() { TestCollectionBuilder builder = _composition.WithCollectionBuilder(); builder.Append(); builder.Append(); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1)); } [Test] public void CannotAppendInvalidTypeToBUilder() { TestCollectionBuilder builder = _composition.WithCollectionBuilder(); ////builder.Append(); // does not compile Assert.Throws(() => builder.Append(new[] { typeof(Resolved4) })); } [Test] public void CanRemoveFromBuilder() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .Remove(); Assert.IsTrue(builder.Has()); Assert.IsFalse(builder.Has()); Assert.IsFalse(builder.Has()); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1)); } [Test] public void CanRemoveMissingFromBuilder() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .Remove(); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } [Test] public void CannotRemoveFromBuilderOnceCollectionIsCreated() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); Assert.Throws(() => builder.Remove()); } [Test] public void CanInsertIntoBuilder() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .Insert(); Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved3), typeof(Resolved1), typeof(Resolved2)); } [Test] public void CannotInsertIntoBuilderOnceCollectionIsCreated() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); Assert.Throws(() => builder.Insert()); } [Test] public void CanInsertDuplicateIntoBuilderAndDeDuplicate() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .Insert(); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2), typeof(Resolved1)); } [Test] public void CanInsertIntoEmptyBuilder() { TestCollectionBuilder builder = _composition.WithCollectionBuilder(); builder.Insert(); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2)); } [Test] public void CannotInsertIntoBuilderAtWrongIndex() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); Assert.Throws(() => builder.Insert(99)); Assert.Throws(() => builder.Insert(-1)); } [Test] public void CanInsertIntoBuilderBefore() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .InsertBefore(); Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved3), typeof(Resolved2)); } [Test] public void CanInsertIntoBuilderAfter() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .InsertAfter(); Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved3), typeof(Resolved2)); } [Test] public void CanInsertIntoBuilderAfterLast() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .InsertAfter(); Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2), typeof(Resolved3)); } [Test] public void CannotInsertIntoBuilderBeforeOnceCollectionIsCreated() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); Assert.Throws(() => builder.InsertBefore()); } [Test] public void CanInsertDuplicateIntoBuilderBeforeAndDeDuplicate() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .InsertBefore(); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2), typeof(Resolved1)); } [Test] public void CannotInsertIntoBuilderBeforeMissing() { TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append(); Assert.Throws(() => builder.InsertBefore()); } [Test] public void ScopeBuilderCreatesScopedCollection() { _composition.WithCollectionBuilder() .Append() .Append(); // CreateCollection creates a new collection each time // but the container manages the scope, so to test the scope // the collection must come from the container. IServiceProvider factory = _composition.CreateServiceProvider(); using (IServiceScope scope = factory.CreateScope()) { TestCollection col1 = scope.ServiceProvider.GetRequiredService(); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); TestCollection col2 = scope.ServiceProvider.GetRequiredService(); AssertCollection(col2, typeof(Resolved1), typeof(Resolved2)); AssertSameCollection(scope.ServiceProvider, col1, col2); } } [Test] public void TransientBuilderCreatesTransientCollection() { _composition.WithCollectionBuilder() .Append() .Append(); // CreateCollection creates a new collection each time // but the container manages the scope, so to test the scope // the collection must come from the container. IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col1 = factory.GetRequiredService(); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); TestCollection col2 = factory.GetRequiredService(); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); AssertNotSameCollection(col1, col2); } [Test] public void BuilderRespectsTypesOrder() { TestCollectionBuilderTransient builder = _composition.WithCollectionBuilder() .Append() .Insert() .InsertBefore(); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col1 = builder.CreateCollection(factory); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2), typeof(Resolved3)); } [Test] public void ScopeBuilderRespectsContainerScope() { _composition.WithCollectionBuilder() .Append() .Append(); // CreateCollection creates a new collection each time // but the container manages the scope, so to test the scope // the collection must come from the container/ IServiceProvider serviceProvider = _composition.CreateServiceProvider(); TestCollection col1A, col1B; using (IServiceScope scope = serviceProvider.CreateScope()) { col1A = scope.ServiceProvider.GetRequiredService(); col1B = scope.ServiceProvider.GetRequiredService(); AssertCollection(col1A, typeof(Resolved1), typeof(Resolved2)); AssertCollection(col1B, typeof(Resolved1), typeof(Resolved2)); AssertSameCollection(serviceProvider, col1A, col1B); } TestCollection col2; using (IServiceScope scope = serviceProvider.CreateScope()) { col2 = scope.ServiceProvider.GetRequiredService(); // NOTE: We must assert here so that the lazy collection is resolved // within this service provider scope, else if you resolve the collection // after the service provider scope is disposed, you'll get an object // disposed error (expected). AssertCollection(col2, typeof(Resolved1), typeof(Resolved2)); } AssertNotSameCollection(col1A, col2); } [Test] public void WeightedBuilderCreatesWeightedCollection() { TestCollectionBuilderWeighted builder = _composition.WithCollectionBuilder() .Add() .Add(); IServiceProvider factory = _composition.CreateServiceProvider(); TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2), typeof(Resolved1)); } [Test] public void WeightedBuilderSetWeight() { var builder = _composition.WithCollectionBuilder() .Add() .Add(); builder.SetWeight(10); var factory = _composition.CreateServiceProvider(); var col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } private static void AssertCollection(IEnumerable col, params Type[] expected) { Resolved[] colA = col.ToArray(); Assert.AreEqual(expected.Length, colA.Length); for (int i = 0; i < expected.Length; i++) { Assert.IsInstanceOf(expected[i], colA[i]); } } private static void AssertSameCollection(IServiceProvider factory, IEnumerable col1, IEnumerable col2) { Assert.AreSame(col1, col2); Resolved[] col1A = col1.ToArray(); Resolved[] col2A = col2.ToArray(); Assert.AreEqual(col1A.Length, col2A.Length); // Ensure each item in each collection is the same but also // resolve each item from the factory to ensure it's also the same since // it should have the same lifespan. for (int i = 0; i < col1A.Length; i++) { Assert.AreSame(col1A[i], col2A[i]); object itemA = factory.GetRequiredService(col1A[i].GetType()); object itemB = factory.GetRequiredService(col2A[i].GetType()); Assert.AreSame(itemA, itemB); } } private static void AssertNotSameCollection(IEnumerable col1, IEnumerable col2) { Assert.AreNotSame(col1, col2); Resolved[] col1A = col1.ToArray(); Resolved[] col2A = col2.ToArray(); Assert.AreEqual(col1A.Length, col2A.Length); for (int i = 0; i < col1A.Length; i++) { Assert.AreNotSame(col1A[i], col2A[i]); } } public abstract class Resolved { } public class Resolved1 : Resolved { } [Weight(50)] // default is 100 public class Resolved2 : Resolved { } public class Resolved3 : Resolved { } public class Resolved4 // not! : Resolved { } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilder : OrderedCollectionBuilderBase { protected override TestCollectionBuilder This => this; } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilderTransient : OrderedCollectionBuilderBase { protected override TestCollectionBuilderTransient This => this; protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient; // transient } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilderScope : OrderedCollectionBuilderBase { protected override TestCollectionBuilderScope This => this; protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Scoped; } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilderWeighted : WeightedCollectionBuilderBase { protected override TestCollectionBuilderWeighted This => this; } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollection : BuilderCollectionBase { public TestCollection(Func> items) : base(items) { } } } }