Move test projects from src/ to tests/ (#11357)
* Update gitignore * Move csproj * Update project references * Update solutions * Update build scripts * Tests used to share editorconfig with projects in src * Fix broken tests. * Stop copying around .editorconfig merged root one with linting * csharp_style_expression_bodied -> suggestion * Move StyleCop rulesets to matching directories and update shared build properties * Remove legacy build files, update NuGet.cofig and solution files * Restore myget source * Clean up .gitignore * Update .gitignore * Move new test classes to tests after merge * Gitignore + nuget config * Move new test Co-authored-by: Ronald Barendse <ronald@barend.se>
This commit is contained in:
341
tests/Umbraco.Tests.Integration/Implementations/TestHelper.cs
Normal file
341
tests/Umbraco.Tests.Integration/Implementations/TestHelper.cs
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Hosting.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Diagnostics;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Net;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Runtime;
|
||||
using Umbraco.Cms.Infrastructure.Persistence;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Web.Common.AspNetCore;
|
||||
using Umbraco.Extensions;
|
||||
using File = System.IO.File;
|
||||
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Implementations
|
||||
{
|
||||
public class TestHelper : TestHelperBase
|
||||
{
|
||||
private IBackOfficeInfo _backOfficeInfo;
|
||||
private IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IApplicationShutdownRegistry _hostingLifetime;
|
||||
private readonly IIpResolver _ipResolver;
|
||||
private readonly IWebHostEnvironment _hostEnvironment;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private string _tempWorkingDir;
|
||||
|
||||
public TestHelper()
|
||||
: base(typeof(TestHelper).Assembly)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Connection.RemoteIpAddress = IPAddress.Parse("127.0.0.1");
|
||||
_httpContextAccessor = Mock.Of<IHttpContextAccessor>(x => x.HttpContext == httpContext);
|
||||
_ipResolver = new AspNetCoreIpResolver(_httpContextAccessor);
|
||||
|
||||
string contentRoot = Assembly.GetExecutingAssembly().GetRootDirectorySafe();
|
||||
var hostEnvironment = new Mock<IWebHostEnvironment>();
|
||||
|
||||
// This must be the assembly name for the WebApplicationFactory to work.
|
||||
hostEnvironment.Setup(x => x.ApplicationName).Returns(GetType().Assembly.GetName().Name);
|
||||
hostEnvironment.Setup(x => x.ContentRootPath).Returns(() => contentRoot);
|
||||
hostEnvironment.Setup(x => x.ContentRootFileProvider).Returns(() => new PhysicalFileProvider(contentRoot));
|
||||
hostEnvironment.Setup(x => x.WebRootPath).Returns(() => WorkingDirectory);
|
||||
hostEnvironment.Setup(x => x.WebRootFileProvider).Returns(() => new PhysicalFileProvider(WorkingDirectory));
|
||||
|
||||
// We also need to expose it as the obsolete interface since netcore's WebApplicationFactory casts it.
|
||||
hostEnvironment.As<Microsoft.AspNetCore.Hosting.IHostingEnvironment>();
|
||||
|
||||
_hostEnvironment = hostEnvironment.Object;
|
||||
|
||||
_hostingLifetime = new AspNetCoreApplicationShutdownRegistry(Mock.Of<IHostApplicationLifetime>());
|
||||
ConsoleLoggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
|
||||
ProfilingLogger = new ProfilingLogger(ConsoleLoggerFactory.CreateLogger<ProfilingLogger>(), Profiler);
|
||||
}
|
||||
|
||||
public IUmbracoBootPermissionChecker UmbracoBootPermissionChecker { get; } =
|
||||
new TestUmbracoBootPermissionChecker();
|
||||
|
||||
public AppCaches AppCaches { get; } = new AppCaches(
|
||||
NoAppCache.Instance,
|
||||
NoAppCache.Instance,
|
||||
new IsolatedCaches(type => NoAppCache.Instance));
|
||||
|
||||
public ILoggerFactory ConsoleLoggerFactory { get; private set; }
|
||||
|
||||
public IProfilingLogger ProfilingLogger { get; private set; }
|
||||
|
||||
public IProfiler Profiler { get; } = new NoopProfiler();
|
||||
|
||||
public IHttpContextAccessor GetHttpContextAccessor() => _httpContextAccessor;
|
||||
|
||||
public IWebHostEnvironment GetWebHostEnvironment() => _hostEnvironment;
|
||||
|
||||
public override IDbProviderFactoryCreator DbProviderFactoryCreator =>
|
||||
new SqlServerDbProviderFactoryCreator(DbProviderFactories.GetFactory, Options.Create(new GlobalSettings()));
|
||||
|
||||
public override IBulkSqlInsertProvider BulkSqlInsertProvider => new SqlServerBulkSqlInsertProvider();
|
||||
|
||||
public override IMarchal Marchal { get; } = new AspNetCoreMarchal();
|
||||
|
||||
public override IHostingEnvironment GetHostingEnvironment()
|
||||
=> _hostingEnvironment ??= new TestHostingEnvironment(
|
||||
GetIOptionsMonitorOfHostingSettings(),
|
||||
GetIOptionsMonitorOfWebRoutingSettings(),
|
||||
_hostEnvironment);
|
||||
|
||||
private IOptionsMonitor<HostingSettings> GetIOptionsMonitorOfHostingSettings()
|
||||
{
|
||||
var hostingSettings = new HostingSettings();
|
||||
return Mock.Of<IOptionsMonitor<HostingSettings>>(x => x.CurrentValue == hostingSettings);
|
||||
}
|
||||
|
||||
private IOptionsMonitor<WebRoutingSettings> GetIOptionsMonitorOfWebRoutingSettings()
|
||||
{
|
||||
var webRoutingSettings = new WebRoutingSettings();
|
||||
return Mock.Of<IOptionsMonitor<WebRoutingSettings>>(x => x.CurrentValue == webRoutingSettings);
|
||||
}
|
||||
|
||||
public override IApplicationShutdownRegistry GetHostingEnvironmentLifetime() => _hostingLifetime;
|
||||
|
||||
public override IIpResolver GetIpResolver() => _ipResolver;
|
||||
|
||||
/// <summary>
|
||||
/// Some test files are copied to the /bin (/bin/debug) on build, this is a utility to return their physical path based on a virtual path name
|
||||
/// </summary>
|
||||
public override string MapPathForTestFiles(string relativePath)
|
||||
{
|
||||
if (!relativePath.StartsWith("~/"))
|
||||
{
|
||||
throw new ArgumentException("relativePath must start with '~/'", nameof(relativePath));
|
||||
}
|
||||
|
||||
string codeBase = typeof(TestHelperBase).Assembly.CodeBase;
|
||||
var uri = new Uri(codeBase);
|
||||
string path = uri.LocalPath;
|
||||
string bin = Path.GetDirectoryName(path);
|
||||
|
||||
return relativePath.Replace("~/", bin + "/");
|
||||
}
|
||||
|
||||
public void AssertPropertyValuesAreEqual(object actual, object expected, Func<IEnumerable, IEnumerable> sorter = null, string[] ignoreProperties = null)
|
||||
{
|
||||
const int dateDeltaMilliseconds = 1000; // 1s
|
||||
|
||||
PropertyInfo[] properties = expected.GetType().GetProperties();
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
// Ignore properties that are attributed with EditorBrowsableState.Never.
|
||||
EditorBrowsableAttribute att = property.GetCustomAttribute<EditorBrowsableAttribute>(false);
|
||||
if (att != null && att.State == EditorBrowsableState.Never)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore explicitly ignored properties.
|
||||
if (ignoreProperties != null && ignoreProperties.Contains(property.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
object actualValue = property.GetValue(actual, null);
|
||||
object expectedValue = property.GetValue(expected, null);
|
||||
|
||||
AssertAreEqual(property, expectedValue, actualValue, sorter, dateDeltaMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertListsAreEqual(PropertyInfo property, IEnumerable expected, IEnumerable actual, Func<IEnumerable, IEnumerable> sorter = null, int dateDeltaMilliseconds = 0)
|
||||
{
|
||||
if (sorter == null)
|
||||
{
|
||||
// this is pretty hackerific but saves us some code to write
|
||||
sorter = enumerable =>
|
||||
{
|
||||
// semi-generic way of ensuring any collection of IEntity are sorted by Ids for comparison
|
||||
var entities = enumerable.OfType<IEntity>().ToList();
|
||||
return entities.Count > 0 ? (IEnumerable)entities.OrderBy(x => x.Id) : entities;
|
||||
};
|
||||
}
|
||||
|
||||
var expectedListEx = sorter(expected).Cast<object>().ToList();
|
||||
var actualListEx = sorter(actual).Cast<object>().ToList();
|
||||
|
||||
if (actualListEx.Count != expectedListEx.Count)
|
||||
{
|
||||
Assert.Fail("Collection {0}.{1} does not match. Expected IEnumerable containing {2} elements but was IEnumerable containing {3} elements", property.PropertyType.Name, property.Name, expectedListEx.Count, actualListEx.Count);
|
||||
}
|
||||
|
||||
for (int i = 0; i < actualListEx.Count; i++)
|
||||
{
|
||||
AssertAreEqual(property, expectedListEx[i], actualListEx[i], sorter, dateDeltaMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertAreEqual(PropertyInfo property, object expected, object actual, Func<IEnumerable, IEnumerable> sorter = null, int dateDeltaMilliseconds = 0)
|
||||
{
|
||||
if (!(expected is string) && expected is IEnumerable enumerable)
|
||||
{
|
||||
// sort property collection by alias, not by property ids
|
||||
// on members, built-in properties don't have ids (always zero)
|
||||
if (expected is PropertyCollection)
|
||||
{
|
||||
sorter = e => ((PropertyCollection)e).OrderBy(x => x.Alias);
|
||||
}
|
||||
|
||||
// compare lists
|
||||
AssertListsAreEqual(property, (IEnumerable)actual, enumerable, sorter, dateDeltaMilliseconds);
|
||||
}
|
||||
else if (expected is DateTime expectedDateTime)
|
||||
{
|
||||
// compare date & time with delta
|
||||
var actualDateTime = (DateTime)actual;
|
||||
double delta = (actualDateTime - expectedDateTime).TotalMilliseconds;
|
||||
Assert.IsTrue(Math.Abs(delta) <= dateDeltaMilliseconds, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expected, actual);
|
||||
}
|
||||
else if (expected is Property expectedProperty)
|
||||
{
|
||||
// compare values
|
||||
var actualProperty = (Property)actual;
|
||||
IPropertyValue[] expectedPropertyValues = expectedProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray();
|
||||
IPropertyValue[] actualPropertyValues = actualProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray();
|
||||
if (expectedPropertyValues.Length != actualPropertyValues.Length)
|
||||
{
|
||||
Assert.Fail($"{property.DeclaringType.Name}.{property.Name}: Expected {expectedPropertyValues.Length} but got {actualPropertyValues.Length}.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < expectedPropertyValues.Length; i++)
|
||||
{
|
||||
// This is not pretty, but since a property value can be a datetime we can't just always compare them as is.
|
||||
// This is made worse by the fact that PublishedValue is not always set, meaning we can't lump it all into the same if block
|
||||
if (expectedPropertyValues[i].EditedValue is DateTime expectedEditDateTime)
|
||||
{
|
||||
var actualEditDateTime = (DateTime)actualPropertyValues[i].EditedValue;
|
||||
AssertDateTime(expectedEditDateTime, actualEditDateTime, $"{property.DeclaringType.Name}.{property.Name}: Expected draft value \"{expectedPropertyValues[i].EditedValue}\" but got \"{actualPropertyValues[i].EditedValue}\".", dateDeltaMilliseconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(expectedPropertyValues[i].EditedValue, actualPropertyValues[i].EditedValue, $"{property.DeclaringType.Name}.{property.Name}: Expected draft value \"{expectedPropertyValues[i].EditedValue}\" but got \"{actualPropertyValues[i].EditedValue}\".");
|
||||
}
|
||||
|
||||
if (expectedPropertyValues[i].PublishedValue is DateTime expectedPublishDateTime)
|
||||
{
|
||||
var actualPublishedDateTime = (DateTime)actualPropertyValues[i].PublishedValue;
|
||||
AssertDateTime(expectedPublishDateTime, actualPublishedDateTime, $"{property.DeclaringType.Name}.{property.Name}: Expected published value \"{expectedPropertyValues[i].PublishedValue}\" but got \"{actualPropertyValues[i].PublishedValue}\".", dateDeltaMilliseconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(expectedPropertyValues[i].PublishedValue, actualPropertyValues[i].PublishedValue, $"{property.DeclaringType.Name}.{property.Name}: Expected published value \"{expectedPropertyValues[i].PublishedValue}\" but got \"{actualPropertyValues[i].PublishedValue}\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (expected is IDataEditor expectedEditor)
|
||||
{
|
||||
Assert.IsInstanceOf<IDataEditor>(actual);
|
||||
var actualEditor = (IDataEditor)actual;
|
||||
Assert.AreEqual(expectedEditor.Alias, actualEditor.Alias);
|
||||
|
||||
// what else shall we test?
|
||||
}
|
||||
else
|
||||
{
|
||||
// directly compare values
|
||||
Assert.AreEqual(
|
||||
expected,
|
||||
actual,
|
||||
"Property {0}.{1} does not match. Expected: {2} but was: {3}",
|
||||
property.DeclaringType.Name,
|
||||
property.Name,
|
||||
expected?.ToString() ?? "<null>",
|
||||
actual?.ToString() ?? "<null>");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertDateTime(DateTime expected, DateTime actual, string failureMessage, int dateDeltaMiliseconds = 0)
|
||||
{
|
||||
double delta = (actual - expected).TotalMilliseconds;
|
||||
Assert.IsTrue(Math.Abs(delta) <= dateDeltaMiliseconds, failureMessage);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
Try(() =>
|
||||
{
|
||||
if (Directory.Exists(path) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
});
|
||||
|
||||
Try(() =>
|
||||
{
|
||||
if (Directory.Exists(path) == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Directory.Delete(path, true);
|
||||
});
|
||||
}
|
||||
|
||||
public static void TryAssert(Action action, int maxTries = 5, int waitMilliseconds = 200) =>
|
||||
Try<AssertionException>(action, maxTries, waitMilliseconds);
|
||||
|
||||
public static void Try(Action action, int maxTries = 5, int waitMilliseconds = 200) =>
|
||||
Try<Exception>(action, maxTries, waitMilliseconds);
|
||||
|
||||
public static void Try<T>(Action action, int maxTries = 5, int waitMilliseconds = 200)
|
||||
where T : Exception
|
||||
{
|
||||
int tries = 0;
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
break;
|
||||
}
|
||||
catch (T)
|
||||
{
|
||||
if (tries++ > maxTries)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
Thread.Sleep(waitMilliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user