Files
Umbraco-CMS/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs

404 lines
16 KiB
C#
Raw Normal View History

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NUnit.Framework;
2020-03-30 21:53:30 +11:00
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Composing.LightInject;
using Umbraco.Core.Configuration;
2020-03-30 21:53:30 +11:00
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
2020-03-30 21:53:30 +11:00
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Scoping;
2020-03-30 21:53:30 +11:00
using Umbraco.Core.Strings;
using Umbraco.Tests.Common.Builders;
using Umbraco.Tests.Integration.Extensions;
using Umbraco.Tests.Integration.Implementations;
using Umbraco.Extensions;
using Umbraco.Tests.Testing;
using Umbraco.Web;
using ILogger = Umbraco.Core.Logging.ILogger;
using Umbraco.Core.Runtime;
using Umbraco.Core;
using Moq;
using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using System.Data.SqlClient;
using System.Data.Common;
using System.IO;
namespace Umbraco.Tests.Integration.Testing
{
/// <summary>
/// Abstract class for integration tests
/// </summary>
/// <remarks>
/// This will use a Host Builder to boot and install Umbraco ready for use
/// </remarks>
[SingleThreaded]
[NonParallelizable]
public abstract class UmbracoIntegrationTest
{
public static LightInjectContainer CreateUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory)
{
var container = UmbracoServiceProviderFactory.CreateServiceContainer();
2020-05-12 17:14:39 +10:00
serviceProviderFactory = new UmbracoServiceProviderFactory(container, false);
var umbracoContainer = serviceProviderFactory.GetContainer();
return umbracoContainer;
}
private Action _testTeardown = null;
private Action _fixtureTeardown = null;
public void OnTestTearDown(Action tearDown) => _testTeardown = tearDown;
public void OnFixtureTearDown(Action tearDown) => _fixtureTeardown = tearDown;
[OneTimeTearDown]
public void FixtureTearDown() => _fixtureTeardown?.Invoke();
[TearDown]
public virtual void TearDown() => _testTeardown?.Invoke();
[SetUp]
public virtual async Task Setup()
{
var hostBuilder = CreateHostBuilder();
var host = await hostBuilder.StartAsync();
Services = host.Services;
var app = new ApplicationBuilder(host.Services);
Configure(app);
}
#region Generic Host Builder and Runtime
/// <summary>
/// Create the Generic Host and execute startup ConfigureServices/Configure calls
/// </summary>
/// <returns></returns>
public virtual IHostBuilder CreateHostBuilder()
{
UmbracoContainer = CreateUmbracoContainer(out var serviceProviderFactory);
_serviceProviderFactory = serviceProviderFactory;
var hostBuilder = Host.CreateDefaultBuilder()
// IMPORTANT: We Cannot use UseStartup, there's all sorts of threads about this with testing. Although this can work
// if you want to setup your tests this way, it is a bit annoying to do that as the WebApplicationFactory will
// create separate Host instances. So instead of UseStartup, we just call ConfigureServices/Configure ourselves,
// and in the case of the UmbracoTestServerTestBase it will use the ConfigureWebHost to Configure the IApplicationBuilder directly.
//.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(GetType()); })
.UseUmbraco(_serviceProviderFactory)
.ConfigureAppConfiguration((context, configBuilder) =>
{
context.HostingEnvironment = TestHelper.GetWebHostEnvironment();
Configuration = context.Configuration;
configBuilder.AddInMemoryCollection(InMemoryConfiguration);
})
.ConfigureServices((hostContext, services) =>
{
ConfigureServices(services);
});
return hostBuilder;
}
/// <summary>
/// Creates a <see cref="CoreRuntime"/> instance for testing and registers an event handler for database install
/// </summary>
/// <param name="configs"></param>
/// <param name="umbracoVersion"></param>
/// <param name="ioHelper"></param>
/// <param name="logger"></param>
/// <param name="profiler"></param>
/// <param name="hostingEnvironment"></param>
/// <param name="backOfficeInfo"></param>
/// <param name="typeFinder"></param>
/// <param name="requestCache"></param>
/// <param name="dbProviderFactoryCreator"></param>
/// <returns></returns>
public CoreRuntime CreateTestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper,
ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo,
ITypeFinder typeFinder, IRequestCache requestCache, IDbProviderFactoryCreator dbProviderFactoryCreator)
{
return CreateTestRuntime(configs,
umbracoVersion,
ioHelper,
logger,
profiler,
hostingEnvironment,
backOfficeInfo,
typeFinder,
requestCache,
dbProviderFactoryCreator,
TestHelper.MainDom, // SimpleMainDom
UseTestLocalDb // DB Installation event handler
);
}
/// <summary>
/// Creates a <see cref="CoreRuntime"/> instance for testing and registers an event handler for database install
/// </summary>
/// <param name="configs"></param>
/// <param name="umbracoVersion"></param>
/// <param name="ioHelper"></param>
/// <param name="logger"></param>
/// <param name="profiler"></param>
/// <param name="hostingEnvironment"></param>
/// <param name="backOfficeInfo"></param>
/// <param name="typeFinder"></param>
/// <param name="requestCache"></param>
/// <param name="dbProviderFactoryCreator"></param>
/// <param name="mainDom"></param>
/// <param name="eventHandler">The event handler used for DB installation</param>
/// <returns></returns>
public static CoreRuntime CreateTestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper,
ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo,
ITypeFinder typeFinder, IRequestCache requestCache, IDbProviderFactoryCreator dbProviderFactoryCreator,
IMainDom mainDom, Action<CoreRuntime, RuntimeEssentialsEventArgs> eventHandler)
{
var runtime = new CoreRuntime(
configs,
umbracoVersion,
ioHelper,
logger,
profiler,
Mock.Of<IUmbracoBootPermissionChecker>(),
hostingEnvironment,
backOfficeInfo,
dbProviderFactoryCreator,
mainDom,
typeFinder,
requestCache);
runtime.RuntimeEssentials += (sender, args) => eventHandler(sender, args);
return runtime;
}
#endregion
#region IStartup
public virtual void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(TestHelper.DbProviderFactoryCreator);
var webHostEnvironment = TestHelper.GetWebHostEnvironment();
services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment);
// Add it!
services.AddUmbracoConfiguration(Configuration);
services.AddUmbracoCore(webHostEnvironment, UmbracoContainer, GetType().Assembly, NoAppCache.Instance, TestHelper.GetLoggingConfiguration(), CreateTestRuntime, out _);
services.AddUmbracoWebComponents();
services.AddUmbracoRuntimeMinifier(Configuration);
services.AddUmbracoBackOffice();
services.AddUmbracoBackOfficeIdentity();
services.AddMvc();
services.AddSingleton<ILogger>(new ConsoleLogger(new MessageTemplates()));
CustomTestSetup(services);
}
public virtual void Configure(IApplicationBuilder app)
{
Services.GetRequiredService<IUmbracoContextFactory>().EnsureUmbracoContext();
// get the currently set ptions
var testOptions = TestOptionAttributeBase.GetTestOptions<UmbracoTestAttribute>();
if (testOptions.Boot)
{
app.UseUmbracoCore();
}
}
#endregion
#region LocalDb
private static readonly object _dbLocker = new object();
private static LocalDbTestDatabase _dbInstance;
/// <summary>
/// Event handler for the <see cref="CoreRuntime.RuntimeEssentials"/> to install the database
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
protected void UseTestLocalDb(CoreRuntime sender, RuntimeEssentialsEventArgs args)
{
// This will create a db, install the schema and ensure the app is configured to run
InstallTestLocalDb(args.DatabaseFactory, sender.ProfilingLogger, sender.Configs.Global(), sender.State, TestHelper.WorkingDirectory, out var connectionString);
TestDBConnectionString = connectionString;
InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = TestDBConnectionString;
}
/// <summary>
/// Get or create an instance of <see cref="LocalDbTestDatabase"/>
/// </summary>
/// <param name="filesPath"></param>
/// <param name="logger"></param>
/// <param name="globalSettings"></param>
/// <param name="dbFactory"></param>
/// <returns></returns>
/// <remarks>
/// There must only be ONE instance shared between all tests in a session
/// </remarks>
private static LocalDbTestDatabase GetOrCreateDatabase(string filesPath, ILogger logger, IGlobalSettings globalSettings, IUmbracoDatabaseFactory dbFactory)
{
lock (_dbLocker)
{
if (_dbInstance != null) return _dbInstance;
var localDb = new LocalDb();
if (localDb.IsAvailable == false)
throw new InvalidOperationException("LocalDB is not available.");
_dbInstance = new LocalDbTestDatabase(logger, globalSettings, localDb, filesPath, dbFactory);
return _dbInstance;
}
}
/// <summary>
/// Creates a LocalDb instance to use for the test
/// </summary>
/// <param name="serviceProvider"></param>
/// <param name="workingDirectory"></param>
/// <param name="integrationTest"></param>
/// <param name="connectionString"></param>
/// <returns></returns>
private void InstallTestLocalDb(
IUmbracoDatabaseFactory databaseFactory, IProfilingLogger logger, IGlobalSettings globalSettings,
IRuntimeState runtimeState, string workingDirectory, out string connectionString)
{
connectionString = null;
var dbFilePath = Path.Combine(workingDirectory, "LocalDb");
// get the currently set db options
var testOptions = TestOptionAttributeBase.GetTestOptions<UmbracoTestAttribute>();
if (testOptions.Database == UmbracoTestOptions.Database.None)
return;
// need to manually register this factory
DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance);
if (!Directory.Exists(dbFilePath))
Directory.CreateDirectory(dbFilePath);
var db = GetOrCreateDatabase(dbFilePath, logger, globalSettings, databaseFactory);
switch (testOptions.Database)
{
case UmbracoTestOptions.Database.NewSchemaPerTest:
// New DB + Schema
var newSchemaDbId = db.AttachSchema();
// Add teardown callback
OnTestTearDown(() => db.Detach(newSchemaDbId));
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
if (!databaseFactory.Configured)
{
databaseFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer);
}
// re-run the runtime level check
runtimeState.DetermineRuntimeLevel();
Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level);
break;
case UmbracoTestOptions.Database.NewEmptyPerTest:
var newEmptyDbId = db.AttachEmpty();
// Add teardown callback
OnTestTearDown(() => db.Detach(newEmptyDbId));
break;
case UmbracoTestOptions.Database.NewSchemaPerFixture:
// New DB + Schema
var newSchemaFixtureDbId = db.AttachSchema();
// Add teardown callback
OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbId));
break;
case UmbracoTestOptions.Database.NewEmptyPerFixture:
throw new NotImplementedException();
//// Add teardown callback
//integrationTest.OnFixtureTearDown(() => db.Detach());
break;
default:
throw new ArgumentOutOfRangeException(nameof(testOptions), testOptions, null);
}
connectionString = db.ConnectionString;
}
#endregion
2020-07-01 17:42:39 +02:00
2020-03-30 21:53:30 +11:00
#region Common services
protected LightInjectContainer UmbracoContainer { get; private set; }
private UmbracoServiceProviderFactory _serviceProviderFactory;
protected virtual T GetRequiredService<T>() => Services.GetRequiredService<T>();
public Dictionary<string, string> InMemoryConfiguration { get; } = new Dictionary<string, string>();
public IConfiguration Configuration { get; protected set; }
public TestHelper TestHelper = new TestHelper();
protected string TestDBConnectionString { get; private set; }
protected virtual Action<IServiceCollection> CustomTestSetup => services => { };
/// <summary>
/// Returns the DI container
/// </summary>
protected IServiceProvider Services { get; set; }
/// <summary>
/// Returns the <see cref="IScopeProvider"/>
/// </summary>
protected IScopeProvider ScopeProvider => Services.GetRequiredService<IScopeProvider>();
/// <summary>
/// Returns the <see cref="IScopeAccessor"/>
/// </summary>
protected IScopeAccessor ScopeAccessor => Services.GetRequiredService<IScopeAccessor>();
/// <summary>
/// Returns the <see cref="ILogger"/>
/// </summary>
protected ILogger Logger => Services.GetRequiredService<ILogger>();
2020-03-30 21:53:30 +11:00
protected AppCaches AppCaches => Services.GetRequiredService<AppCaches>();
protected IIOHelper IOHelper => Services.GetRequiredService<IIOHelper>();
protected IShortStringHelper ShortStringHelper => Services.GetRequiredService<IShortStringHelper>();
protected IGlobalSettings GlobalSettings => Services.GetRequiredService<IGlobalSettings>();
protected IMapperCollection Mappers => Services.GetRequiredService<IMapperCollection>();
#endregion
#region Builders
protected GlobalSettingsBuilder GlobalSettingsBuilder = new GlobalSettingsBuilder();
protected UserBuilder UserBuilder = new UserBuilder();
protected UserGroupBuilder UserGroupBuilder = new UserGroupBuilder();
#endregion
}
}