* Update to dotnet 9 and update nuget packages * Update umbraco code version * Update Directory.Build.props Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> * Include preview version in pipeline * update template projects * update global json with specific version * Update version.json to v15 * Rename TrimStart and TrimEnd to string specific * Rename to Exact * Update global.json Co-authored-by: Ronald Barendse <ronald@barend.se> * Remove includePreviewVersion * Rename to trim exact * Add new Hybridcache project * Add tests * Start implementing PublishedContent.cs * Implement repository for content * Refactor to use async everywhere * Add cache refresher * make public as needed for serialization * Use content type cache to get content type out * Refactor to use ContentCacheNode model, that goes in the memory cache * Remove content node kit as its not needed * Implement tests for ensuring caching * Implement better asserts * Implement published property * Refactor to use mapping * Rename to document tests * Update to test properties * Create more tests * Refactor mock tests into own file * Update property test * Fix published version of content * Change default cache level to elements * Refactor to always have draft * Refactor to not use PublishedModelFactory * Added tests * Added and updated tests * Fixed tests * Don't return empty object with id * More tests * Added key * Another key * Refactor CacheService to be responsible for using the hybrid cache * Use notification handler to remove deleted content from cache * Add more tests for missing functions * Implement missing methods * Remove HasContent as it pertains to routing * Fik up test * formatting * refactor variable names * Implement variant tests * Map all the published content properties * Get item out of cache first, to assert updated * Implement member cache * Add member test * Implement media cache * Implement property tests for media tests * Refactor tests to use extension method * Add more media tests * Refactor properties to no longer have element caching * Don't use property cache level * Start implementing seeding * Only seed when main * Add Immutable for performance * Implement permanent seeding of content * Implement cache settings * Implement tests for seeding * Update package version * start refactoring nurepo * Refactor so draft & published nodes are cached individually * Refactor RefreshContent to take node instead of IContent * Refactor media to also use cache nodes * Remove member from repo as it isn't cached * Refactor media to not include preview, as media has no draft * create new benchmark project * POC Integration benchmarks with custom api controllers * Start implementing content picker tests * Implement domain cache * Rework content cache to implement interface * Start implementing elements cache * Implement published snapshot service * Publish snapshot tests * Use snapshot for elements cache * Create test proving we don't clear cache when updating content picker * Clear entire elements cache * Remove properties from element cache, when content gets updated. * Rename methods to async * Refactor to use old cache interfaces instead of new ones * Remove snapshot, as it is no longer needed * Fix tests building * Refactor domaincache to not have snapshots * Delete benchmarks * Delete benchmarks * Add HybridCacheProject to Umbraco * Add comment to route value transformer * Implement is draft * remove snapshot from property * V15 updated the hybrid caching integration tests to use ContentEditingService (#16947) * Added builder extension withParentKey * Created builder with ContentEditingService * Added usage of the ContentEditingService to SETUP * Started using ContentEditingService builder in tests * Updated builder extensions * Fixed builder * Clean up * Clean up, not done * Added Ids * Remove entries from cache on delete * Fix up seeding logic * Don't register hybrid cache twice * Change seeded entry options * Update hybrid cache package * Fix up published property to work with delivery api again * Fix dependency injection to work with tests * Fix naming * Dont make caches nullable * Make content node sealed * Remove path and other unused from content node * Remove hacky 2 phase ctor * Refactor to actually set content templates * Remove umbraco context * Remove "HasBy" methods * rename property data * Delete obsolete legacy stuff * Add todo for making expiration configurable * Add todo in UmbracoContext * Add clarifying comment in content factory * Remove xml stuff from published property * Fix according to review * Make content type cache injectible * Make content type cache injectible * Rename to database cache repository * Rename to document cache * Add TODO * Refactor to async * Rename to async * Make everything async * Remove duplicate line from json schema * Move Hybrid cache project * Remove leftover file * Refactor to use keys * Refactor published content to no longer have content data, as it is on the node itself * Refactor to member to use proper content node ctor * Move tests to own folder * Add immutable objects to property and content data for performance * Make property data public * Fix member caching to be singleton * Obsolete GetContentType * Remove todo * Fix naming * Fix lots of exposed errors due to scope test * Add final scope tests * Rename to document cache service * Rename test files * Create new doc type tests * Add ignore to tests * Start implementing refresh for content type save * Clear contenttype cache when contenttype is updated * Fix test Teh contenttype is not upated unless the property is dirty * Use init for ContentSourceDto * Fix get by key in PublishedContentTypeCache * Remove ContentType from PublishedContentTypeCache when contenttype is deleted * Update to preview 7 * Fix versions * Increase timeout for sqlite integration tests * Undo timeout increase * Try and undo init change to ContentSourceDto * That wasn't it chief * Try and make DomainAndUrlsTests non NonParallelizable * Update versions * Only run cache tests on linux for now --------- Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com> Co-authored-by: Ronald Barendse <ronald@barend.se> Co-authored-by: Andreas Zerbst <andr317c@live.dk> Co-authored-by: Sven Geusens <sge@umbraco.dk> Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Co-authored-by: nikolajlauridsen <nikolajlauridsen@protonmail.ch>
355 lines
14 KiB
C#
355 lines
14 KiB
C#
using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
using Asp.Versioning;
|
|
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.Api.Common.Attributes;
|
|
using Umbraco.Cms.Api.Delivery.Controllers.Content;
|
|
using Umbraco.Cms.Api.Management.Controllers;
|
|
using Umbraco.Cms.Api.Management.Controllers.ModelsBuilder;
|
|
using Umbraco.Cms.Api.Management.DependencyInjection;
|
|
using Umbraco.Cms.Core;
|
|
using Umbraco.Cms.Core.Cache;
|
|
using Umbraco.Cms.Core.Composing;
|
|
using Umbraco.Cms.Core.Services;
|
|
using Umbraco.Cms.Core.Web;
|
|
using Umbraco.Cms.Core.Persistence.Repositories;
|
|
using Umbraco.Cms.Persistence.Sqlite;
|
|
using Umbraco.Cms.Persistence.SqlServer;
|
|
using Umbraco.Cms.Tests.Common.Testing;
|
|
using Umbraco.Cms.Tests.Integration.DependencyInjection;
|
|
using Umbraco.Cms.Tests.Integration.Testing;
|
|
using Umbraco.Cms.Web.Common.Controllers;
|
|
using Umbraco.Cms.Web.Website.Controllers;
|
|
|
|
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 => Factory.Services.GetRequiredService<LinkGenerator>();
|
|
|
|
protected WebApplicationFactory<UmbracoTestServerTestBase> Factory { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Hook for altering UmbracoBuilder setup
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Can also be used for registering test doubles.
|
|
/// </remarks>
|
|
protected virtual void CustomTestSetup(IUmbracoBuilder builder)
|
|
{
|
|
}
|
|
|
|
protected virtual void CustomTestAuthSetup(IServiceCollection services)
|
|
{
|
|
// Add a test auth scheme with a test auth handler to authn and assign the user
|
|
services.AddAuthentication(TestAuthHandler.TestAuthenticationScheme)
|
|
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
|
|
TestAuthHandler.TestAuthenticationScheme, options => { });
|
|
}
|
|
|
|
[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<UmbracoTestServerTestBase>(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 =>
|
|
{
|
|
services.AddSingleton<IWebProfilerRepository, TestWebProfilerRepository>();
|
|
|
|
CustomTestAuthSetup(services);
|
|
});
|
|
});
|
|
|
|
Client = Factory.CreateClient(new WebApplicationFactoryClientOptions
|
|
{
|
|
AllowAutoRedirect = false,
|
|
BaseAddress = new Uri("https://localhost/", UriKind.Absolute)
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepare a url before using <see cref="Client"/>.
|
|
/// This returns the url but also sets the HttpContext.request into to use this url.
|
|
/// </summary>
|
|
/// <returns>The string URL of the controller action.</returns>
|
|
protected string PrepareApiControllerUrl<T>(Expression<Func<T, object>> methodSelector)
|
|
where T : UmbracoApiController
|
|
{
|
|
var url = LinkGenerator.GetUmbracoApiService(methodSelector);
|
|
return PrepareUrl(url);
|
|
}
|
|
|
|
protected string GetManagementApiUrl<T>(Expression<Func<T, object>> methodSelector)
|
|
where T : ManagementApiControllerBase
|
|
{
|
|
MethodInfo? method = ExpressionHelper.GetMethodInfo(methodSelector);
|
|
IDictionary<string, object?> methodParams = ExpressionHelper.GetMethodParams(methodSelector) ?? new Dictionary<string, object?>();
|
|
|
|
// Remove the CancellationToken from the method params, this is automatically added by the framework
|
|
// So we do not want to add this to the query string
|
|
var cancellationTokenKey = methodParams.FirstOrDefault(x => x.Value is CancellationToken).Key;
|
|
if (cancellationTokenKey is not null)
|
|
{
|
|
methodParams.Remove(cancellationTokenKey);
|
|
}
|
|
|
|
|
|
methodParams["version"] = method?.GetCustomAttribute<MapToApiVersionAttribute>()?.Versions?.First().MajorVersion.ToString();
|
|
if (method == null)
|
|
{
|
|
throw new MissingMethodException(
|
|
$"Could not find the method {methodSelector} on type {typeof(T)} or the result ");
|
|
}
|
|
|
|
var controllerName = ControllerExtensions.GetControllerName(typeof(T));
|
|
|
|
return LinkGenerator.GetUmbracoControllerUrl(method.Name, controllerName, null, methodParams);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepare a url before using <see cref="Client"/>.
|
|
/// This returns the url but also sets the HttpContext.request into to use this url.
|
|
/// </summary>
|
|
/// <returns>The string URL of the controller action.</returns>
|
|
protected string PrepareSurfaceControllerUrl<T>(Expression<Func<T, object>> methodSelector)
|
|
where T : SurfaceController
|
|
{
|
|
var url = LinkGenerator.GetUmbracoSurfaceUrl(methodSelector);
|
|
return PrepareUrl(url);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepare a url before using <see cref="Client"/>.
|
|
/// This returns the url but also sets the HttpContext.request into to use this url.
|
|
/// </summary>
|
|
/// <returns>The string URL of the controller action.</returns>
|
|
protected string PrepareUrl(string url)
|
|
{
|
|
IUmbracoContextFactory umbracoContextFactory = GetRequiredService<IUmbracoContextFactory>();
|
|
IHttpContextAccessor httpContextAccessor = GetRequiredService<IHttpContextAccessor>();
|
|
|
|
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()
|
|
.ConfigureUmbracoDefaults()
|
|
.ConfigureAppConfiguration((context, configBuilder) =>
|
|
{
|
|
context.HostingEnvironment = TestHelper.GetWebHostEnvironment();
|
|
configBuilder.Sources.Clear();
|
|
configBuilder.AddInMemoryCollection(InMemoryConfiguration);
|
|
configBuilder.AddConfiguration(GlobalSetupTeardown.TestConfiguration);
|
|
|
|
Configuration = configBuilder.Build();
|
|
})
|
|
.ConfigureWebHost(builder =>
|
|
{
|
|
builder.ConfigureServices((context, services) =>
|
|
{
|
|
context.HostingEnvironment = TestHelper.GetWebHostEnvironment();
|
|
|
|
ConfigureServices(services);
|
|
ConfigureTestServices(services);
|
|
services.AddUnique(CreateLoggerFactory());
|
|
|
|
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>());
|
|
}
|
|
});
|
|
|
|
// call startup
|
|
builder.Configure(Configure);
|
|
})
|
|
.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<T>() => Factory.Services.GetRequiredService<T>();
|
|
|
|
protected void ConfigureServices(IServiceCollection services)
|
|
{
|
|
services.AddTransient<TestUmbracoDatabaseFactoryProvider>();
|
|
|
|
Core.Hosting.IHostingEnvironment hostingEnvironment = TestHelper.GetHostingEnvironment();
|
|
|
|
TypeLoader typeLoader = services.AddTypeLoader(
|
|
GetType().Assembly,
|
|
hostingEnvironment,
|
|
TestHelper.ConsoleLoggerFactory,
|
|
AppCaches.NoCache,
|
|
Configuration,
|
|
TestHelper.Profiler);
|
|
|
|
services.AddLogger(TestHelper.GetWebHostEnvironment(), Configuration);
|
|
|
|
var builder = new UmbracoBuilder(services, Configuration, typeLoader, TestHelper.ConsoleLoggerFactory, TestHelper.Profiler, AppCaches.NoCache, hostingEnvironment);
|
|
builder.Services.AddTransient<IHostedService>(sp => new TestDatabaseHostedLifecycleService(() => UseTestDatabase(sp)));
|
|
|
|
builder
|
|
.AddConfiguration()
|
|
.AddUmbracoCore()
|
|
.AddWebComponents()
|
|
.AddNuCache()
|
|
.AddUmbracoHybridCache()
|
|
.AddBackOfficeCore()
|
|
.AddBackOfficeAuthentication()
|
|
.AddBackOfficeIdentity()
|
|
.AddMembersIdentity()
|
|
// .AddBackOfficeAuthorizationPolicies(TestAuthHandler.TestAuthenticationScheme)
|
|
.AddMvcAndRazor(mvcBuilding: mvcBuilder =>
|
|
{
|
|
// Adds Umbraco.Web.Common
|
|
mvcBuilder.AddApplicationPart(typeof(RenderController).Assembly);
|
|
|
|
// Adds Umbraco.Web.Website
|
|
mvcBuilder.AddApplicationPart(typeof(SurfaceController).Assembly);
|
|
|
|
// Adds Umbraco.Cms.Api.ManagementApi
|
|
mvcBuilder.AddApplicationPart(typeof(ModelsBuilderControllerBase).Assembly);
|
|
|
|
// Adds Umbraco.Cms.Api.DeliveryApi
|
|
mvcBuilder.AddApplicationPart(typeof(ContentApiItemControllerBase).Assembly);
|
|
|
|
// Adds Umbraco.Tests.Integration
|
|
mvcBuilder.AddApplicationPart(typeof(UmbracoTestServerTestBase).Assembly);
|
|
|
|
CustomMvcSetup(mvcBuilder);
|
|
})
|
|
.AddWebServer()
|
|
.AddWebsite()
|
|
.AddUmbracoSqlServerSupport()
|
|
.AddUmbracoSqliteSupport()
|
|
.AddDeliveryApi()
|
|
.AddUmbracoManagementApi()
|
|
.AddComposers()
|
|
.AddTestServices(TestHelper); // This is the important one!
|
|
|
|
CustomTestSetup(builder);
|
|
|
|
|
|
builder.Build();
|
|
}
|
|
|
|
protected virtual void CustomMvcSetup(IMvcBuilder mvcBuilder)
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hook for registering test doubles.
|
|
/// </summary>
|
|
protected virtual void ConfigureTestServices(IServiceCollection services)
|
|
{
|
|
|
|
}
|
|
|
|
protected void Configure(IApplicationBuilder app)
|
|
{
|
|
app.UseUmbraco()
|
|
.WithMiddleware(u =>
|
|
{
|
|
u.UseBackOffice();
|
|
u.UseWebsite();
|
|
})
|
|
.WithEndpoints(u =>
|
|
{
|
|
u.UseBackOfficeEndpoints();
|
|
u.UseWebsiteEndpoints();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
public class TestDatabaseHostedLifecycleService : IHostedLifecycleService
|
|
{
|
|
private readonly Action _action;
|
|
|
|
public TestDatabaseHostedLifecycleService(Action action)
|
|
{
|
|
_action = action;
|
|
}
|
|
|
|
public Task StartAsync(CancellationToken cancellationToken)=> Task.CompletedTask;
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
|
|
|
public Task StartedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
|
|
|
public Task StartingAsync(CancellationToken cancellationToken)
|
|
{
|
|
_action();
|
|
return Task.CompletedTask;
|
|
|
|
}
|
|
public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
|
|
|
public Task StoppingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
|
}
|