using System; using System.Linq.Expressions; using System.Net.Http; using System.Reflection; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.DependencyInjection; using Umbraco.Cms.Tests.Integration.Testing; using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Cms.Web.Common.Controllers; using Umbraco.Cms.Web.Website.Controllers; using Umbraco.Extensions; namespace Umbraco.Cms.Tests.Integration.TestServerTest { [TestFixture] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console, Boot = true)] public abstract class UmbracoTestServerTestBase : UmbracoIntegrationTestBase { protected HttpClient Client { get; private set; } protected LinkGenerator LinkGenerator { get; private set; } protected WebApplicationFactory Factory { get; private set; } [SetUp] public void Setup() { /* * It's worth noting that our usage of WebApplicationFactory is non-standard, * the intent is that your Startup.ConfigureServices is called just like * when the app starts up, then replacements are registered in this class with * builder.ConfigureServices (builder.ConfigureTestServices has hung around from before the * generic host switchover). * * This is currently a pain to refactor towards due to UmbracoBuilder+TypeFinder+TypeLoader setup but * we should get there one day. * * However we need to separate the testing framework we provide for downstream projects from our own tests. * We cannot use the Umbraco.Web.UI startup yet as that is not available downstream. * * See https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests */ var factory = new UmbracoWebApplicationFactory(CreateHostBuilder); // additional host configuration for web server integration tests Factory = factory.WithWebHostBuilder(builder => { // Otherwise inferred as $(SolutionDir)/Umbraco.Tests.Integration (note lack of src/tests) builder.UseContentRoot(Assembly.GetExecutingAssembly().GetRootDirectorySafe()); // Executes after the standard ConfigureServices method builder.ConfigureTestServices(services => // Add a test auth scheme with a test auth handler to authn and assign the user services.AddAuthentication(TestAuthHandler.TestAuthenticationScheme) .AddScheme(TestAuthHandler.TestAuthenticationScheme, options => { })); }); Client = Factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); LinkGenerator = Factory.Services.GetRequiredService(); } /// /// Prepare a url before using . /// This returns the url but also sets the HttpContext.request into to use this url. /// /// The string URL of the controller action. protected string PrepareApiControllerUrl(Expression> methodSelector) where T : UmbracoApiController { var url = LinkGenerator.GetUmbracoApiService(methodSelector); return PrepareUrl(url); } /// /// Prepare a url before using . /// This returns the url but also sets the HttpContext.request into to use this url. /// /// The string URL of the controller action. protected string PrepareSurfaceControllerUrl(Expression> methodSelector) where T : SurfaceController { var url = LinkGenerator.GetUmbracoSurfaceUrl(methodSelector); return PrepareUrl(url); } /// /// Prepare a url before using . /// This returns the url but also sets the HttpContext.request into to use this url. /// /// The string URL of the controller action. protected string PrepareUrl(string url) { IUmbracoContextFactory umbracoContextFactory = GetRequiredService(); IHttpContextAccessor httpContextAccessor = GetRequiredService(); httpContextAccessor.HttpContext = new DefaultHttpContext { Request = { Scheme = "https", Host = new HostString("localhost", 80), Path = url, QueryString = new QueryString(string.Empty) } }; umbracoContextFactory.EnsureUmbracoContext(); return url; } private IHostBuilder CreateHostBuilder() { IHostBuilder hostBuilder = Host.CreateDefaultBuilder() .ConfigureAppConfiguration((context, configBuilder) => { context.HostingEnvironment = TestHelper.GetWebHostEnvironment(); configBuilder.Sources.Clear(); configBuilder.AddInMemoryCollection(InMemoryConfiguration); Configuration = configBuilder.Build(); }) /* It is important that ConfigureWebHost is called before ConfigureServices, this is consistent with the host setup * found in Program.cs and avoids nasty surprises. * * e.g. the registration for RefreshingRazorViewEngine requires that IWebHostEnvironment is registered * at the point in time that the service collection is snapshotted. */ .ConfigureWebHost(builder => { // need to configure the IWebHostEnvironment too builder.ConfigureServices((c, s) => c.HostingEnvironment = TestHelper.GetWebHostEnvironment()); // call startup builder.Configure(Configure); }) .ConfigureServices((_, services) => { ConfigureServices(services); ConfigureTestSpecificServices(services); 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()); } }) .UseDefaultServiceProvider(cfg => { // These default to true *if* WebHostEnvironment.EnvironmentName == Development // When running tests, EnvironmentName used to be null on the mock that we register into services. // Enable opt in for tests so that validation occurs regardless of environment name. // Would be nice to have this on for UmbracoIntegrationTest also but requires a lot more effort to resolve issues. cfg.ValidateOnBuild = true; cfg.ValidateScopes = true; }); return hostBuilder; } protected virtual IServiceProvider Services => Factory.Services; protected virtual T GetRequiredService() => Factory.Services.GetRequiredService(); private void ConfigureServices(IServiceCollection services) { services.AddUnique(CreateLoggerFactory()); services.AddTransient(); Core.Hosting.IHostingEnvironment hostingEnvironment = TestHelper.GetHostingEnvironment(); TypeLoader typeLoader = services.AddTypeLoader( GetType().Assembly, hostingEnvironment, TestHelper.ConsoleLoggerFactory, AppCaches.NoCache, Configuration, TestHelper.Profiler); var builder = new UmbracoBuilder(services, Configuration, typeLoader, TestHelper.ConsoleLoggerFactory, TestHelper.Profiler, AppCaches.NoCache, hostingEnvironment); builder .AddConfiguration() .AddUmbracoCore() .AddWebComponents() .AddNuCache() .AddRuntimeMinifier() .AddBackOfficeCore() .AddBackOfficeAuthentication() .AddBackOfficeIdentity() .AddMembersIdentity() .AddBackOfficeAuthorizationPolicies(TestAuthHandler.TestAuthenticationScheme) .AddPreviewSupport() .AddMvcAndRazor(mvcBuilding: mvcBuilder => { // Adds Umbraco.Web.BackOffice mvcBuilder.AddApplicationPart(typeof(ContentController).Assembly); // Adds Umbraco.Web.Common mvcBuilder.AddApplicationPart(typeof(RenderController).Assembly); // Adds Umbraco.Web.Website mvcBuilder.AddApplicationPart(typeof(SurfaceController).Assembly); // Adds Umbraco.Tests.Integration mvcBuilder.AddApplicationPart(typeof(UmbracoTestServerTestBase).Assembly); }) .AddWebServer() .AddWebsite() .AddTestServices(TestHelper) // This is the important one! .Build(); } private void Configure(IApplicationBuilder app) { UseTestDatabase(app); app.UseUmbraco() .WithMiddleware(u => { u.UseBackOffice(); u.UseWebsite(); }) .WithEndpoints(u => { u.UseBackOfficeEndpoints(); u.UseWebsiteEndpoints(); }); } } }