Fixup CoreRuntime so it starts after Startup.Configure
Makes integration tests play nice with Components as RuntimeLevel will be correct
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -191,6 +191,7 @@ src/Umbraco.Web.UI/Umbraco/telemetrics-id.umb
|
||||
/src/Umbraco.Web.UI.NetCore/App_Data/Smidge/Cache/*
|
||||
/src/Umbraco.Web.UI.NetCore/umbraco/logs
|
||||
|
||||
src/Umbraco.Tests.Integration/umbraco/Data/
|
||||
src/Umbraco.Tests.Integration/umbraco/logs/
|
||||
|
||||
src/Umbraco.Tests.Integration/Views/
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Umbraco.Core.Composing
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends the <see cref="IHostBuilder"/> to enable Umbraco to be used as the service container.
|
||||
/// Extends the <see cref="IHostBuilder"/> to add CoreRuntime as a HostedService
|
||||
/// </summary>
|
||||
public static class HostBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Assigns a custom service provider factory to use Umbraco's container
|
||||
/// Adds CoreRuntime as HostedService
|
||||
/// </summary>
|
||||
/// <param name="builder"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// <para>When running the site should be called before ConfigureWebDefaults.</para>
|
||||
///
|
||||
/// <para>
|
||||
/// When testing should be called after ConfigureWebDefaults to ensure UseTestDatabase is called before CoreRuntime
|
||||
/// starts or we initialize components with incorrect run level.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IHostBuilder UseUmbraco(this IHostBuilder builder)
|
||||
{
|
||||
_ = builder.ConfigureServices((context, services) =>
|
||||
services.AddSingleton<IHostedService>(factory => factory.GetRequiredService<IRuntime>()));
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,16 +135,16 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
_entitySerializer = entitySerializer;
|
||||
_publishedModelFactory = publishedModelFactory;
|
||||
|
||||
// we always want to handle repository events, configured or not
|
||||
// assuming no repository event will trigger before the whole db is ready
|
||||
// (ideally we'd have Upgrading.App vs Upgrading.Data application states...)
|
||||
InitializeRepositoryEvents();
|
||||
|
||||
_lifeTime.ApplicationInit += OnApplicationInit;
|
||||
}
|
||||
|
||||
internal void OnApplicationInit(object sender, EventArgs e)
|
||||
{
|
||||
// we always want to handle repository events, configured or not
|
||||
// assuming no repository event will trigger before the whole db is ready
|
||||
// (ideally we'd have Upgrading.App vs Upgrading.Data application states...)
|
||||
InitializeRepositoryEvents();
|
||||
|
||||
// however, the cache is NOT available until we are configured, because loading
|
||||
// content (and content types) from database cannot be consistent (see notes in "Handle
|
||||
// Notifications" region), so
|
||||
|
||||
@@ -43,7 +43,6 @@ namespace Umbraco.Tests.Integration
|
||||
var testHelper = new TestHelper();
|
||||
|
||||
var hostBuilder = new HostBuilder()
|
||||
.UseUmbraco()
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
var webHostEnvironment = testHelper.GetWebHostEnvironment();
|
||||
@@ -61,14 +60,16 @@ namespace Umbraco.Tests.Integration
|
||||
hostContext.Configuration,
|
||||
testHelper.Profiler);
|
||||
|
||||
var builder = new UmbracoBuilder(services, hostContext.Configuration, typeLoader, testHelper.ConsoleLoggerFactory);
|
||||
var builder = new UmbracoBuilder(services, hostContext.Configuration, typeLoader,
|
||||
testHelper.ConsoleLoggerFactory);
|
||||
builder.Services.AddUnique<AppCaches>(AppCaches.NoCache);
|
||||
builder.AddConfiguration()
|
||||
.AddUmbracoCore()
|
||||
.Build();
|
||||
.AddUmbracoCore()
|
||||
.Build();
|
||||
|
||||
services.AddRouting(); // LinkGenerator
|
||||
});
|
||||
})
|
||||
.UseUmbraco();
|
||||
|
||||
var host = await hostBuilder.StartAsync();
|
||||
var app = new ApplicationBuilder(host.Services);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
@@ -11,7 +9,6 @@ using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Extensions;
|
||||
@@ -22,8 +19,7 @@ using Umbraco.Core.DependencyInjection;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Runtime;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
|
||||
namespace Umbraco.Tests.Integration.TestServerTest
|
||||
@@ -75,11 +71,14 @@ namespace Umbraco.Tests.Integration.TestServerTest
|
||||
// call startup
|
||||
builder.Configure(app =>
|
||||
{
|
||||
UseTestLocalDb(app.ApplicationServices);
|
||||
UseTestDatabase(app.ApplicationServices);
|
||||
Services = app.ApplicationServices;
|
||||
Configure(app);
|
||||
});
|
||||
}).UseEnvironment(Environments.Development);
|
||||
|
||||
}).UseEnvironment(Environments.Development);
|
||||
|
||||
builder.UseUmbraco(); // Ensures CoreRuntime.StartAsync is called, must be after ConfigureWebHost
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
@@ -8,13 +9,20 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
{
|
||||
public static ITestDatabase Create(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
|
||||
{
|
||||
return string.IsNullOrEmpty(Environment.GetEnvironmentVariable("UmbracoIntegrationTestConnectionString"))
|
||||
? CreateLocalDb(filesPath, loggerFactory, dbFactory.Create())
|
||||
: CreateSqlDeveloper(loggerFactory, dbFactory.Create());
|
||||
var connectionString = Environment.GetEnvironmentVariable("UmbracoIntegrationTestConnectionString");
|
||||
|
||||
return string.IsNullOrEmpty(connectionString)
|
||||
? CreateLocalDb(filesPath, loggerFactory, dbFactory)
|
||||
: CreateSqlDeveloper(loggerFactory, dbFactory, connectionString);
|
||||
}
|
||||
|
||||
private static ITestDatabase CreateLocalDb(string filesPath, ILoggerFactory loggerFactory, IUmbracoDatabaseFactory dbFactory)
|
||||
private static ITestDatabase CreateLocalDb(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
|
||||
{
|
||||
if (!Directory.Exists(filesPath))
|
||||
{
|
||||
Directory.CreateDirectory(filesPath);
|
||||
}
|
||||
|
||||
var localDb = new LocalDb();
|
||||
|
||||
if (!localDb.IsAvailable)
|
||||
@@ -22,22 +30,21 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
throw new InvalidOperationException("LocalDB is not available.");
|
||||
}
|
||||
|
||||
return new LocalDbTestDatabase(loggerFactory, localDb, filesPath, dbFactory);
|
||||
return new LocalDbTestDatabase(loggerFactory, localDb, filesPath, dbFactory.Create());
|
||||
}
|
||||
|
||||
private static ITestDatabase CreateSqlDeveloper(ILoggerFactory loggerFactory, IUmbracoDatabaseFactory dbFactory)
|
||||
private static ITestDatabase CreateSqlDeveloper(ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory, string connectionString)
|
||||
{
|
||||
// NOTE: Example setup for Linux box.
|
||||
// $ export SA_PASSWORD=Foobar123!
|
||||
// $ export UmbracoIntegrationTestConnectionString="Server=localhost,1433;User Id=sa;Password=$SA_PASSWORD;"
|
||||
// $ docker run -e 'ACCEPT_EULA=Y' -e "SA_PASSWORD=$SA_PASSWORD" -e 'MSSQL_PID=Developer' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-latest-ubuntu
|
||||
var connectionString = Environment.GetEnvironmentVariable("UmbracoIntegrationTestConnectionString");
|
||||
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
throw new InvalidOperationException("ENV: UmbracoIntegrationTestConnectionString is not set");
|
||||
}
|
||||
|
||||
return new SqlDeveloperTestDatabase(loggerFactory, dbFactory, connectionString);
|
||||
return new SqlDeveloperTestDatabase(loggerFactory, dbFactory.Create(), connectionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,10 +97,10 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
public virtual void Setup()
|
||||
{
|
||||
InMemoryConfiguration[Constants.Configuration.ConfigGlobal + ":" + nameof(GlobalSettings.InstallEmptyDatabase)] = "true";
|
||||
var hostBuilder = CreateHostBuilder();
|
||||
var hostBuilder = CreateHostBuilder()
|
||||
.UseUmbraco(); // This ensures CoreRuntime.StartAsync will be called (however it's a mock if boot = false)
|
||||
|
||||
var host = hostBuilder.Start();
|
||||
|
||||
Services = host.Services;
|
||||
var app = new ApplicationBuilder(host.Services);
|
||||
|
||||
@@ -113,8 +113,7 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
{
|
||||
try
|
||||
{
|
||||
var testOptions = TestOptionAttributeBase.GetTestOptions<UmbracoTestAttribute>();
|
||||
switch (testOptions.Logger)
|
||||
switch (TestOptions.Logger)
|
||||
{
|
||||
case UmbracoTestOptions.Logger.Mock:
|
||||
return NullLoggerFactory.Instance;
|
||||
@@ -138,15 +137,13 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
/// <returns></returns>
|
||||
public virtual IHostBuilder CreateHostBuilder()
|
||||
{
|
||||
|
||||
var testOptions = TestOptionAttributeBase.GetTestOptions<UmbracoTestAttribute>();
|
||||
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()
|
||||
|
||||
.ConfigureAppConfiguration((context, configBuilder) =>
|
||||
{
|
||||
context.HostingEnvironment = TestHelper.GetWebHostEnvironment();
|
||||
@@ -157,12 +154,13 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
})
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddTransient(_ => CreateLoggerFactory());
|
||||
ConfigureServices(services);
|
||||
services.AddUnique(CreateLoggerFactory());
|
||||
|
||||
if (!testOptions.Boot)
|
||||
if (!TestOptions.Boot)
|
||||
{
|
||||
// If boot is false, we don't want the CoreRuntime hosted service to start
|
||||
// So we replace it with a Mock
|
||||
services.AddUnique(Mock.Of<IRuntime>());
|
||||
}
|
||||
});
|
||||
@@ -224,11 +222,15 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
|
||||
public virtual void Configure(IApplicationBuilder app)
|
||||
{
|
||||
UseTestLocalDb(app.ApplicationServices);
|
||||
UseTestDatabase(app.ApplicationServices);
|
||||
|
||||
Services.GetRequiredService<IBackOfficeSecurityFactory>().EnsureBackOfficeSecurity();
|
||||
Services.GetRequiredService<IUmbracoContextFactory>().EnsureUmbracoContext();
|
||||
app.UseUmbracoCore();
|
||||
if (TestOptions.Boot)
|
||||
{
|
||||
Services.GetRequiredService<IBackOfficeSecurityFactory>().EnsureBackOfficeSecurity();
|
||||
Services.GetRequiredService<IUmbracoContextFactory>().EnsureUmbracoContext();
|
||||
}
|
||||
|
||||
app.UseUmbracoCore(); // This no longer starts CoreRuntime, it's very fast
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -239,25 +241,20 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
private static ITestDatabase _dbInstance;
|
||||
private static TestDbMeta _fixtureDbMeta;
|
||||
|
||||
protected void UseTestLocalDb(IServiceProvider serviceProvider)
|
||||
protected void UseTestDatabase(IServiceProvider serviceProvider)
|
||||
{
|
||||
var state = serviceProvider.GetRequiredService<IRuntimeState>();
|
||||
var testDatabaseFactoryProvider = serviceProvider.GetRequiredService<TestUmbracoDatabaseFactoryProvider>();
|
||||
var databaseFactory = serviceProvider.GetRequiredService<IUmbracoDatabaseFactory>();
|
||||
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
|
||||
|
||||
// This will create a db, install the schema and ensure the app is configured to run
|
||||
InstallTestLocalDb(testDatabaseFactoryProvider, databaseFactory, serviceProvider.GetRequiredService<ILoggerFactory>(), state, TestHelper.WorkingDirectory);
|
||||
SetupTestDatabase(testDatabaseFactoryProvider, databaseFactory, loggerFactory, state, TestHelper.WorkingDirectory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or create an instance of <see cref="LocalDbTestDatabase"/>
|
||||
/// Get or create an instance of <see cref="ITestDatabase"/>
|
||||
/// </summary>
|
||||
/// <param name="filesPath"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="loggerFactory"></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>
|
||||
@@ -266,9 +263,12 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
lock (_dbLocker)
|
||||
{
|
||||
if (_dbInstance != null)
|
||||
{
|
||||
return _dbInstance;
|
||||
}
|
||||
|
||||
_dbInstance = TestDatabaseFactory.Create(filesPath, loggerFactory, dbFactory);
|
||||
|
||||
return _dbInstance;
|
||||
}
|
||||
}
|
||||
@@ -276,30 +276,26 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
/// <summary>
|
||||
/// Creates a LocalDb instance to use for the test
|
||||
/// </summary>
|
||||
private void InstallTestLocalDb(
|
||||
private void SetupTestDatabase(
|
||||
TestUmbracoDatabaseFactoryProvider testUmbracoDatabaseFactoryProvider,
|
||||
IUmbracoDatabaseFactory databaseFactory,
|
||||
ILoggerFactory loggerFactory,
|
||||
IRuntimeState runtimeState,
|
||||
string workingDirectory)
|
||||
{
|
||||
var dbFilePath = Path.Combine(workingDirectory, "LocalDb");
|
||||
|
||||
// get the currently set db options
|
||||
var testOptions = TestOptionAttributeBase.GetTestOptions<UmbracoTestAttribute>();
|
||||
|
||||
if (testOptions.Database == UmbracoTestOptions.Database.None)
|
||||
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 dbFilePath = Path.Combine(workingDirectory, "LocalDb");
|
||||
|
||||
var db = GetOrCreateDatabase(dbFilePath, loggerFactory, testUmbracoDatabaseFactoryProvider);
|
||||
|
||||
switch (testOptions.Database)
|
||||
switch (TestOptions.Database)
|
||||
{
|
||||
case UmbracoTestOptions.Database.NewSchemaPerTest:
|
||||
|
||||
@@ -385,7 +381,7 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(testOptions), testOptions, null);
|
||||
throw new ArgumentOutOfRangeException(nameof(TestOptions), TestOptions, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,6 +389,8 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
|
||||
#region Common services
|
||||
|
||||
protected UmbracoTestAttribute TestOptions => TestOptionAttributeBase.GetTestOptions<UmbracoTestAttribute>();
|
||||
|
||||
protected virtual T GetRequiredService<T>() => Services.GetRequiredService<T>();
|
||||
|
||||
public Dictionary<string, string> InMemoryConfiguration { get; } = new Dictionary<string, string>();
|
||||
|
||||
@@ -146,9 +146,7 @@ namespace Umbraco.Core.DependencyInjection
|
||||
builder.Services.AddUnique<IRuntimeState, RuntimeState>();
|
||||
builder.Services.AddUnique<IHostingEnvironment, AspNetCoreHostingEnvironment>();
|
||||
builder.Services.AddUnique<IMainDom, MainDom>();
|
||||
|
||||
builder.Services.AddUnique<IRuntime, CoreRuntime>();
|
||||
builder.Services.AddHostedService(factory => factory.GetRequiredService<IRuntime>());
|
||||
|
||||
builder.AddCoreInitialServices();
|
||||
builder.AddComposers();
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Web.UI.NetCore
|
||||
{
|
||||
x.ClearProviders();
|
||||
})
|
||||
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
|
||||
.UseUmbraco();
|
||||
.UseUmbraco()
|
||||
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user