using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Xml.Linq;
using Examine;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.IO.MediaPathSchemes;
using Umbraco.Core.Logging;
using Umbraco.Core.Logging.Serilog;
using Umbraco.Core.Manifest;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services.Implement;
using Umbraco.Core.Strings;
using Umbraco.Tests.Components;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Stubs;
using Umbraco.Web;
using Umbraco.Web.Services;
using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web.Actions;
using Umbraco.Web.ContentApps;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Trees;
using Umbraco.Core.Composing.CompositionExtensions;
using Umbraco.Core.Hosting;
using Umbraco.Core.Mapping;
using Umbraco.Core.Serialization;
using Umbraco.Web.Composing.CompositionExtensions;
using Umbraco.Web.Hosting;
using Umbraco.Web.Sections;
using Current = Umbraco.Core.Composing.Current;
using FileSystems = Umbraco.Core.IO.FileSystems;
using Umbraco.Web.Templates;
using Umbraco.Web.PropertyEditors;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Net;
using Umbraco.Web.Security;
namespace Umbraco.Tests.Testing
{
///
/// Provides the top-level base class for all Umbraco integration tests.
///
///
/// True unit tests do not need to inherit from this class, but most of Umbraco tests
/// are not true unit tests but integration tests requiring services, databases, etc. This class
/// provides all the necessary environment, through DI. Yes, DI is bad in tests - unit tests.
/// But it is OK in integration tests.
///
public abstract class UmbracoTestBase
{
// this class
// ensures that Current is properly resetted
// ensures that a service container is properly initialized and disposed
// compose the required dependencies according to test options (UmbracoTestAttribute)
//
// everything is virtual (because, why not?)
// starting a test runs like this:
// - SetUp() // when overriding, call base.SetUp() *first* then setup your own stuff
// --- Compose() // when overriding, call base.Commpose() *first* then compose your own stuff
// --- Initialize() // same
// - test runs
// - TearDown() // when overriding, clear you own stuff *then* call base.TearDown()
//
// about attributes
//
// this class defines the SetUp and TearDown methods, with proper attributes, and
// these attributes are *inherited* so classes inheriting from this class should *not*
// add the attributes to SetUp nor TearDown again
//
// this class is *not* marked with the TestFeature attribute because it is *not* a
// test feature, and no test "base" class should be. only actual test feature classes
// should be marked with that attribute.
protected Composition Composition { get; private set; }
protected IFactory Factory { get; private set; }
protected UmbracoTestAttribute Options { get; private set; }
protected static bool FirstTestInSession = true;
protected bool FirstTestInFixture = true;
internal TestObjects TestObjects { get; private set; }
private static TypeLoader _commonTypeLoader;
private TypeLoader _featureTypeLoader;
#region Accessors
protected ILogger Logger => Factory.GetInstance();
protected IIOHelper IOHelper { get; private set; }
protected IDataTypeService DataTypeService => Factory.GetInstance();
protected IPasswordHasher PasswordHasher => Factory.GetInstance();
protected Lazy PropertyEditorCollection => new Lazy(() => Factory.GetInstance());
protected ILocalizationService LocalizationService => Factory.GetInstance();
protected ILocalizedTextService LocalizedTextService { get; private set; }
protected IShortStringHelper ShortStringHelper => Factory?.GetInstance() ?? TestHelper.ShortStringHelper;
protected IUmbracoVersion UmbracoVersion { get; private set; }
protected ITypeFinder TypeFinder { get; private set; }
protected IProfiler Profiler => Factory.GetInstance();
protected virtual IProfilingLogger ProfilingLogger => Factory.GetInstance();
protected IHostingEnvironment HostingEnvironment => Factory.GetInstance();
protected IIpResolver IpResolver => Factory.GetInstance();
protected IBackOfficeInfo BackOfficeInfo => Factory.GetInstance();
protected AppCaches AppCaches => Factory.GetInstance();
protected virtual ISqlSyntaxProvider SqlSyntax => Factory.GetInstance();
protected IMapperCollection Mappers => Factory.GetInstance();
protected UmbracoMapper Mapper => Factory.GetInstance();
protected IRuntimeState RuntimeState => ComponentTests.MockRuntimeState(RuntimeLevel.Run);
#endregion
#region Setup
[SetUp]
public virtual void SetUp()
{
// should not need this if all other tests were clean
// but hey, never know, better avoid garbage-in
Reset();
// get/merge the attributes marking the method and/or the classes
Options = TestOptionAttributeBase.GetTestOptions();
// FIXME: align to runtimes & components - don't redo everything here !!!! Yes this is getting painful
var (logger, profiler) = GetLoggers(Options.Logger);
var proflogger = new ProfilingLogger(logger, profiler);
IOHelper = TestHelper.IOHelper;
TypeFinder = new TypeFinder(logger);
var appCaches = GetAppCaches();
var globalSettings = SettingsForTests.GetDefaultGlobalSettings();
var hostingSettings = SettingsForTests.GetDefaultHostingSettings();
var settings = SettingsForTests.GetDefaultUmbracoSettings();
IHostingEnvironment hostingEnvironment = new AspNetHostingEnvironment(hostingSettings);
IBackOfficeInfo backOfficeInfo = new AspNetBackOfficeInfo(globalSettings, IOHelper, settings, logger);
IIpResolver ipResolver = new AspNetIpResolver();
UmbracoVersion = new UmbracoVersion(globalSettings);
LocalizedTextService = new LocalizedTextService(new Dictionary>(), logger);
var typeLoader = GetTypeLoader(IOHelper, TypeFinder, appCaches.RuntimeCache, hostingEnvironment, proflogger, Options.TypeLoader);
var register = TestHelper.GetRegister();
Composition = new Composition(register, typeLoader, proflogger, ComponentTests.MockRuntimeState(RuntimeLevel.Run), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache);
Composition.RegisterUnique(IOHelper);
Composition.RegisterUnique(UmbracoVersion);
Composition.RegisterUnique(TypeFinder);
Composition.RegisterUnique(LocalizedTextService);
Composition.RegisterUnique(typeLoader);
Composition.RegisterUnique(logger);
Composition.RegisterUnique(profiler);
Composition.RegisterUnique(proflogger);
Composition.RegisterUnique(appCaches);
Composition.RegisterUnique(hostingEnvironment);
Composition.RegisterUnique(backOfficeInfo);
Composition.RegisterUnique(ipResolver);
Composition.RegisterUnique();
Composition.RegisterUnique();
Composition.RegisterUnique(TestHelper.ShortStringHelper);
TestObjects = new TestObjects(register);
Compose();
Current.Factory = Factory = Composition.CreateFactory();
Initialize();
}
protected virtual void Compose()
{
ComposeMapper(Options.Mapper);
ComposeDatabase(Options.Database);
ComposeApplication(Options.WithApplication);
// etc
ComposeWeb();
ComposeMisc();
// not sure really
Compose(Composition);
}
protected virtual void Compose(Composition composition)
{ }
protected virtual void Initialize()
{
InitializeApplication(Options.WithApplication);
}
#endregion
#region Compose
protected virtual (ILogger, IProfiler) GetLoggers(UmbracoTestOptions.Logger option)
{
ILogger logger;
IProfiler profiler;
switch (option)
{
case UmbracoTestOptions.Logger.Mock:
logger = Mock.Of();
profiler = Mock.Of();
break;
case UmbracoTestOptions.Logger.Serilog:
logger = new SerilogLogger(TestHelper.CoreDebug, IOHelper, TestHelper.Marchal, new FileInfo(TestHelper.MapPathForTest("~/unit-test.config")));
profiler = new LogProfiler(logger);
break;
case UmbracoTestOptions.Logger.Console:
logger = new ConsoleLogger(new MessageTemplates());
profiler = new LogProfiler(logger);
break;
default:
throw new NotSupportedException($"Logger option {option} is not supported.");
}
return (logger, profiler);
}
protected virtual AppCaches GetAppCaches()
{
return AppCaches.Disabled;
}
protected virtual void ComposeWeb()
{
// imported from TestWithSettingsBase
// which was inherited by TestWithApplicationBase so pretty much used everywhere
Umbraco.Web.Composing.Current.UmbracoContextAccessor = new TestUmbracoContextAccessor();
// web
Composition.RegisterUnique(_ => Umbraco.Web.Composing.Current.UmbracoContextAccessor);
Composition.RegisterUnique();
Composition.WithCollectionBuilder();
Composition.DataValueReferenceFactories();
Composition.RegisterUnique();
Composition.RegisterUnique();
Composition.RegisterUnique();
Composition.SetCultureDictionaryFactory();
Composition.Register(f => f.GetInstance().CreateDictionary(), Lifetime.Singleton);
// register back office sections in the order we want them rendered
Composition.WithCollectionBuilder().Append()
.Append()
.Append()
.Append()
.Append()
.Append()
.Append()
.Append();
Composition.RegisterUnique();
Composition.RegisterUnique();
Composition.RegisterUnique();
Composition.RegisterUnique();
Composition.RegisterUnique();
}
protected virtual void ComposeMisc()
{
// what else?
var runtimeStateMock = new Mock();
runtimeStateMock.Setup(x => x.Level).Returns(RuntimeLevel.Run);
Composition.RegisterUnique(f => runtimeStateMock.Object);
// ah...
Composition.WithCollectionBuilder();
Composition.WithCollectionBuilder();
Composition.RegisterUnique();
Composition.RegisterUnique();
Composition.RegisterUnique();
// register empty content apps collection
Composition.WithCollectionBuilder();
// manifest
Composition.ManifestValueValidators();
Composition.ManifestFilters();
}
protected virtual void ComposeMapper(bool configure)
{
if (configure == false) return;
Composition
.ComposeCoreMappingProfiles()
.ComposeWebMappingProfiles();
}
protected virtual TypeLoader GetTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IHostingEnvironment hostingEnvironment, IProfilingLogger logger, UmbracoTestOptions.TypeLoader option)
{
switch (option)
{
case UmbracoTestOptions.TypeLoader.Default:
return _commonTypeLoader ?? (_commonTypeLoader = CreateCommonTypeLoader(ioHelper, typeFinder, runtimeCache, logger, hostingEnvironment));
case UmbracoTestOptions.TypeLoader.PerFixture:
return _featureTypeLoader ?? (_featureTypeLoader = CreateTypeLoader(ioHelper, typeFinder, runtimeCache, logger, hostingEnvironment));
case UmbracoTestOptions.TypeLoader.PerTest:
return CreateTypeLoader(ioHelper, typeFinder, runtimeCache, logger, hostingEnvironment);
default:
throw new ArgumentOutOfRangeException(nameof(option));
}
}
protected virtual TypeLoader CreateTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IProfilingLogger logger, IHostingEnvironment hostingEnvironment)
{
return CreateCommonTypeLoader(ioHelper, typeFinder, runtimeCache, logger, hostingEnvironment);
}
// common to all tests = cannot be overriden
private static TypeLoader CreateCommonTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IProfilingLogger logger, IHostingEnvironment hostingEnvironment)
{
return new TypeLoader(ioHelper, typeFinder, runtimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), logger, false, new[]
{
Assembly.Load("Umbraco.Core"),
Assembly.Load("Umbraco.Web"),
Assembly.Load("Umbraco.Tests"),
Assembly.Load("Umbraco.Infrastructure")
});
}
protected virtual void ComposeDatabase(UmbracoTestOptions.Database option)
{
if (option == UmbracoTestOptions.Database.None) return;
// create the file
// create the schema
}
protected virtual void ComposeSettings()
{
Composition.Configs.Add(SettingsForTests.GetDefaultUmbracoSettings);
Composition.Configs.Add(SettingsForTests.GetDefaultGlobalSettings);
Composition.Configs.Add(SettingsForTests.GetDefaultHostingSettings);
//Composition.Configs.Add(() => new DefaultUserPasswordConfig());
}
protected virtual void ComposeApplication(bool withApplication)
{
ComposeSettings();
if (withApplication == false) return;
// default Datalayer/Repositories/SQL/Database/etc...
Composition.ComposeRepositories();
// register basic stuff that might need to be there for some container resolvers to work
Composition.RegisterUnique(factory => factory.GetInstance().Content);
Composition.RegisterUnique(factory => factory.GetInstance().WebRouting);
Composition.RegisterUnique(factory => ExamineManager.Instance);
Composition.RegisterUnique();
// register filesystems
Composition.RegisterUnique(factory => TestObjects.GetFileSystemsMock());
var logger = Mock.Of();
var scheme = Mock.Of();
var mediaFileSystem = new MediaFileSystem(Mock.Of(), scheme, logger, TestHelper.ShortStringHelper);
Composition.RegisterUnique(factory => mediaFileSystem);
// no factory (noop)
Composition.RegisterUnique();
// register application stuff (database factory & context, services...)
Composition.WithCollectionBuilder()
.AddCoreMappers();
Composition.RegisterUnique(_ => new TransientEventMessagesFactory());
Composition.RegisterUnique(f => new UmbracoDatabaseFactory(
Constants.System.UmbracoConnectionName,
Logger,
new Lazy(f.GetInstance),
TestHelper.GetConfigs(),
TestHelper.DbProviderFactoryCreator,
TestHelper.BulkSqlInsertProvider));
Composition.RegisterUnique(f => f.TryGetInstance().SqlContext);
Composition.WithCollectionBuilder(); // empty
Composition.RegisterUnique(factory
=> TestObjects.GetScopeProvider(factory.TryGetInstance(), factory.TryGetInstance(), factory.TryGetInstance(), factory.TryGetInstance()));
Composition.RegisterUnique(factory => (IScopeAccessor) factory.GetInstance());
Composition.ComposeServices();
// composition root is doing weird things, fix
Composition.RegisterUnique();
Composition.RegisterUnique();
// somehow property editor ends up wanting this
Composition.WithCollectionBuilder();
Composition.RegisterUnique();
// note - don't register collections, use builders
Composition.WithCollectionBuilder();
Composition.RegisterUnique();
Composition.RegisterUnique();
}
#endregion
#region Initialize
protected virtual void InitializeApplication(bool withApplication)
{
if (withApplication == false) return;
TestHelper.InitializeContentDirectories();
}
#endregion
#region TearDown and Reset
[TearDown]
public virtual void TearDown()
{
FirstTestInFixture = false;
FirstTestInSession = false;
Reset();
if (Options.WithApplication)
{
TestHelper.CleanContentDirectories();
TestHelper.CleanUmbracoSettingsConfig();
}
}
protected virtual void Reset()
{
// reset and dispose scopes
// ensures we don't leak an opened database connection
// which would lock eg SqlCe .sdf files
if (Factory?.TryGetInstance() is ScopeProvider scopeProvider)
{
Scope scope;
while ((scope = scopeProvider.AmbientScope) != null)
{
scope.Reset();
scope.Dispose();
}
}
Current.Reset(); // disposes the factory
// reset all other static things that should not be static ;(
UriUtility.ResetAppDomainAppVirtualPath();
SettingsForTests.Reset(); // FIXME: should it be optional?
// clear static events
DocumentRepository.ClearScopeEvents();
MediaRepository.ClearScopeEvents();
MemberRepository.ClearScopeEvents();
ContentTypeService.ClearScopeEvents();
MediaTypeService.ClearScopeEvents();
MemberTypeService.ClearScopeEvents();
}
#endregion
}
}