2020-12-23 11:35:49 +01:00
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System ;
2020-10-09 11:37:25 +02:00
using System.Collections ;
using System.ComponentModel ;
2020-03-24 11:53:56 +11:00
using System.Data.Common ;
2020-03-31 18:01:27 +11:00
using System.IO ;
2020-10-09 11:37:25 +02:00
using System.Linq ;
2020-03-13 14:43:41 +11:00
using System.Net ;
2020-04-03 13:16:01 +11:00
using System.Reflection ;
2020-10-26 10:47:14 +00:00
using System.Threading ;
2020-09-03 12:29:23 +02:00
using Microsoft.AspNetCore.Hosting ;
using Microsoft.AspNetCore.Http ;
2020-09-21 21:06:24 +02:00
using Microsoft.Extensions.FileProviders ;
2020-09-03 12:29:23 +02:00
using Microsoft.Extensions.Hosting ;
2020-09-23 07:17:05 +02:00
using Microsoft.Extensions.Logging ;
2020-09-03 12:29:23 +02:00
using Microsoft.Extensions.Options ;
using Moq ;
2020-10-09 11:37:25 +02:00
using NUnit.Framework ;
2021-02-09 10:22:42 +01:00
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 ;
2020-03-13 14:43:41 +11:00
using Umbraco.Core.Persistence ;
2021-02-09 11:26:22 +01:00
using Umbraco.Extensions ;
2020-03-13 14:43:41 +11:00
using Umbraco.Tests.Common ;
2020-03-27 11:39:17 +01:00
using Umbraco.Web.Common.AspNetCore ;
2020-10-26 10:47:14 +00:00
using File = System . IO . File ;
2021-02-09 10:22:42 +01:00
using IHostingEnvironment = Umbraco . Cms . Core . Hosting . IHostingEnvironment ;
2020-03-13 14:43:41 +11:00
namespace Umbraco.Tests.Integration.Implementations
{
public class TestHelper : TestHelperBase
{
private IBackOfficeInfo _backOfficeInfo ;
2020-03-31 17:27:51 +11:00
private IHostingEnvironment _hostingEnvironment ;
2020-03-26 15:39:20 +11:00
private readonly IApplicationShutdownRegistry _hostingLifetime ;
2020-03-13 14:43:41 +11:00
private readonly IIpResolver _ipResolver ;
private readonly IWebHostEnvironment _hostEnvironment ;
private readonly IHttpContextAccessor _httpContextAccessor ;
2020-03-31 18:01:27 +11:00
private string _tempWorkingDir ;
2020-03-13 14:43:41 +11:00
2020-12-23 11:35:49 +01:00
public TestHelper ( )
: base ( typeof ( TestHelper ) . Assembly )
2020-03-13 14:43:41 +11:00
{
var httpContext = new DefaultHttpContext ( ) ;
httpContext . Connection . RemoteIpAddress = IPAddress . Parse ( "127.0.0.1" ) ;
_httpContextAccessor = Mock . Of < IHttpContextAccessor > ( x = > x . HttpContext = = httpContext ) ;
2020-05-14 17:04:16 +10:00
_ipResolver = new AspNetCoreIpResolver ( _httpContextAccessor ) ;
2020-03-13 14:43:41 +11:00
2020-12-23 11:35:49 +01:00
string contentRoot = Assembly . GetExecutingAssembly ( ) . GetRootDirectorySafe ( ) ;
2020-03-31 17:27:51 +11:00
var hostEnvironment = new Mock < IWebHostEnvironment > ( ) ;
2020-12-23 11:35:49 +01:00
// This must be the assembly name for the WebApplicationFactory to work.
2020-09-16 15:17:42 +02:00
hostEnvironment . Setup ( x = > x . ApplicationName ) . Returns ( GetType ( ) . Assembly . GetName ( ) . Name ) ;
2020-09-02 18:10:29 +10:00
hostEnvironment . Setup ( x = > x . ContentRootPath ) . Returns ( ( ) = > contentRoot ) ;
hostEnvironment . Setup ( x = > x . ContentRootFileProvider ) . Returns ( ( ) = > new PhysicalFileProvider ( contentRoot ) ) ;
2020-03-31 17:27:51 +11:00
hostEnvironment . Setup ( x = > x . WebRootPath ) . Returns ( ( ) = > WorkingDirectory ) ;
2020-09-02 18:10:29 +10:00
hostEnvironment . Setup ( x = > x . WebRootFileProvider ) . Returns ( ( ) = > new PhysicalFileProvider ( WorkingDirectory ) ) ;
2020-12-23 11:35:49 +01:00
// We also need to expose it as the obsolete interface since netcore's WebApplicationFactory casts it.
2020-09-02 18:10:29 +10:00
hostEnvironment . As < Microsoft . AspNetCore . Hosting . IHostingEnvironment > ( ) ;
2020-03-31 17:27:51 +11:00
_hostEnvironment = hostEnvironment . Object ;
2020-03-25 15:06:22 +11:00
2020-03-26 15:39:20 +11:00
_hostingLifetime = new AspNetCoreApplicationShutdownRegistry ( Mock . Of < IHostApplicationLifetime > ( ) ) ;
2020-09-16 15:17:42 +02:00
ConsoleLoggerFactory = LoggerFactory . Create ( builder = > builder . AddConsole ( ) ) ;
2020-11-20 12:24:16 +00:00
ProfilingLogger = new ProfilingLogger ( ConsoleLoggerFactory . CreateLogger < ProfilingLogger > ( ) , Profiler ) ;
2020-03-13 14:43:41 +11:00
}
2020-03-31 18:01:27 +11:00
public override string WorkingDirectory
{
get
{
// For Azure Devops we can only store a database in certain locations so we will need to detect if we are running
// on a build server and if so we'll use the %temp% path.
2020-03-31 18:13:19 +11:00
if ( ! string . IsNullOrWhiteSpace ( Environment . GetEnvironmentVariable ( "System_DefaultWorkingDirectory" ) ) )
2020-03-31 18:01:27 +11:00
{
2020-12-23 11:35:49 +01:00
// We are using Azure Devops!
if ( _tempWorkingDir ! = null )
{
return _tempWorkingDir ;
}
2020-03-31 18:01:27 +11:00
2020-12-23 11:35:49 +01:00
string temp = Path . Combine ( Environment . ExpandEnvironmentVariables ( "%temp%" ) , "UmbracoTemp" ) ;
2020-03-31 18:01:27 +11:00
Directory . CreateDirectory ( temp ) ;
_tempWorkingDir = temp ;
return _tempWorkingDir ;
}
else
{
return base . WorkingDirectory ;
}
}
}
2020-04-14 16:55:54 +01:00
public IUmbracoBootPermissionChecker UmbracoBootPermissionChecker { get ; } =
new TestUmbracoBootPermissionChecker ( ) ;
2020-03-13 14:43:41 +11:00
2020-12-23 11:35:49 +01:00
public AppCaches AppCaches { get ; } = new AppCaches (
NoAppCache . Instance ,
NoAppCache . Instance ,
2020-04-14 16:55:54 +01:00
new IsolatedCaches ( type = > NoAppCache . Instance ) ) ;
2020-03-13 14:43:41 +11:00
2020-09-16 15:17:42 +02:00
public ILoggerFactory ConsoleLoggerFactory { get ; private set ; }
2020-12-23 11:35:49 +01:00
2020-09-15 10:03:56 +02:00
public IProfilingLogger ProfilingLogger { get ; private set ; }
2020-03-13 14:43:41 +11:00
2020-12-24 16:35:59 +11:00
public IProfiler Profiler { get ; } = new NoopProfiler ( ) ;
2020-03-13 14:43:41 +11:00
public IHttpContextAccessor GetHttpContextAccessor ( ) = > _httpContextAccessor ;
public IWebHostEnvironment GetWebHostEnvironment ( ) = > _hostEnvironment ;
2020-04-14 16:55:54 +01:00
public override IDbProviderFactoryCreator DbProviderFactoryCreator = >
2020-05-08 17:30:30 +10:00
new SqlServerDbProviderFactoryCreator ( DbProviderFactories . GetFactory ) ;
2020-03-13 14:43:41 +11:00
public override IBulkSqlInsertProvider BulkSqlInsertProvider = > new SqlServerBulkSqlInsertProvider ( ) ;
public override IMarchal Marchal { get ; } = new AspNetCoreMarchal ( ) ;
public override IBackOfficeInfo GetBackOfficeInfo ( )
{
if ( _backOfficeInfo = = null )
2020-08-24 12:34:37 +02:00
{
2020-09-21 21:06:24 +02:00
var globalSettings = new GlobalSettings ( ) ;
2020-12-23 11:35:49 +01:00
IOptionsMonitor < GlobalSettings > mockedOptionsMonitorOfGlobalSettings = Mock . Of < IOptionsMonitor < GlobalSettings > > ( x = > x . CurrentValue = = globalSettings ) ;
2020-09-03 12:29:23 +02:00
_backOfficeInfo = new AspNetCoreBackOfficeInfo ( mockedOptionsMonitorOfGlobalSettings ) ;
2020-08-24 12:34:37 +02:00
}
2020-03-13 14:43:41 +11:00
return _backOfficeInfo ;
}
2020-03-31 17:27:51 +11:00
public override IHostingEnvironment GetHostingEnvironment ( )
= > _hostingEnvironment ? ? = new TestHostingEnvironment (
2020-09-03 12:29:23 +02:00
GetIOptionsMonitorOfHostingSettings ( ) ,
2021-02-08 11:00:15 +01:00
GetIOptionsMonitorOfWebRoutingSettings ( ) ,
2020-04-03 01:08:52 +11:00
_hostEnvironment ) ;
2020-03-31 17:27:51 +11:00
2020-09-03 12:29:23 +02:00
private IOptionsMonitor < HostingSettings > GetIOptionsMonitorOfHostingSettings ( )
{
2020-09-21 21:06:24 +02:00
var hostingSettings = new HostingSettings ( ) ;
2020-09-03 12:29:23 +02:00
return Mock . Of < IOptionsMonitor < HostingSettings > > ( x = > x . CurrentValue = = hostingSettings ) ;
}
2021-02-08 11:00:15 +01:00
private IOptionsMonitor < WebRoutingSettings > GetIOptionsMonitorOfWebRoutingSettings ( )
{
var webRoutingSettings = new WebRoutingSettings ( ) ;
return Mock . Of < IOptionsMonitor < WebRoutingSettings > > ( x = > x . CurrentValue = = webRoutingSettings ) ;
}
2020-03-26 15:39:20 +11:00
public override IApplicationShutdownRegistry GetHostingEnvironmentLifetime ( ) = > _hostingLifetime ;
2020-03-13 14:43:41 +11:00
public override IIpResolver GetIpResolver ( ) = > _ipResolver ;
2020-03-25 15:06:22 +11:00
2020-04-14 16:55:54 +01:00
/// <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 ( "~/" ) )
2020-12-23 11:35:49 +01:00
{
2020-04-14 16:55:54 +01:00
throw new ArgumentException ( "relativePath must start with '~/'" , nameof ( relativePath ) ) ;
2020-12-23 11:35:49 +01:00
}
2020-04-14 16:55:54 +01:00
2020-12-23 11:35:49 +01:00
string codeBase = typeof ( TestHelperBase ) . Assembly . CodeBase ;
2020-04-14 16:55:54 +01:00
var uri = new Uri ( codeBase ) ;
2020-12-23 11:35:49 +01:00
string path = uri . LocalPath ;
string bin = Path . GetDirectoryName ( path ) ;
2020-04-14 16:55:54 +01:00
return relativePath . Replace ( "~/" , bin + "/" ) ;
}
2020-10-09 11:37:25 +02:00
2020-12-23 11:35:49 +01:00
public void AssertPropertyValuesAreEqual ( object actual , object expected , Func < IEnumerable , IEnumerable > sorter = null , string [ ] ignoreProperties = null )
2020-10-09 11:37:25 +02:00
{
2020-10-21 13:28:13 +02:00
const int dateDeltaMilliseconds = 1000 ; // 1s
2020-10-09 11:37:25 +02:00
2020-12-23 11:35:49 +01:00
PropertyInfo [ ] properties = expected . GetType ( ) . GetProperties ( ) ;
foreach ( PropertyInfo property in properties )
2020-10-09 11:37:25 +02:00
{
2020-12-23 11:35:49 +01:00
// Ignore properties that are attributed with EditorBrowsableState.Never.
EditorBrowsableAttribute att = property . GetCustomAttribute < EditorBrowsableAttribute > ( false ) ;
2020-10-09 11:37:25 +02:00
if ( att ! = null & & att . State = = EditorBrowsableState . Never )
2020-12-23 11:35:49 +01:00
{
2020-10-09 11:37:25 +02:00
continue ;
2020-12-23 11:35:49 +01:00
}
2020-10-09 11:37:25 +02:00
2020-12-23 11:35:49 +01:00
// Ignore explicitly ignored properties.
2020-10-09 11:37:25 +02:00
if ( ignoreProperties ! = null & & ignoreProperties . Contains ( property . Name ) )
2020-12-23 11:35:49 +01:00
{
2020-10-09 11:37:25 +02:00
continue ;
2020-12-23 11:35:49 +01:00
}
2020-10-09 11:37:25 +02:00
2020-12-23 11:35:49 +01:00
object actualValue = property . GetValue ( actual , null ) ;
object expectedValue = property . GetValue ( expected , null ) ;
2020-10-09 11:37:25 +02:00
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 ( ) ;
2020-12-23 11:35:49 +01:00
return entities . Count > 0 ? ( IEnumerable ) entities . OrderBy ( x = > x . Id ) : entities ;
2020-10-09 11:37:25 +02:00
} ;
}
var expectedListEx = sorter ( expected ) . Cast < object > ( ) . ToList ( ) ;
var actualListEx = sorter ( actual ) . Cast < object > ( ) . ToList ( ) ;
if ( actualListEx . Count ! = expectedListEx . Count )
2020-12-23 11:35:49 +01:00
{
2020-10-09 11:37:25 +02:00
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 ) ;
2020-12-23 11:35:49 +01:00
}
2020-10-09 11:37:25 +02:00
2020-12-23 11:35:49 +01:00
for ( int i = 0 ; i < actualListEx . Count ; i + + )
{
2020-10-09 11:37:25 +02:00
AssertAreEqual ( property , expectedListEx [ i ] , actualListEx [ i ] , sorter , dateDeltaMilliseconds ) ;
2020-12-23 11:35:49 +01:00
}
2020-10-09 11:37:25 +02:00
}
2020-10-21 13:28:13 +02:00
private static void AssertAreEqual ( PropertyInfo property , object expected , object actual , Func < IEnumerable , IEnumerable > sorter = null , int dateDeltaMilliseconds = 0 )
2020-10-09 11:37:25 +02:00
{
2020-12-23 11:35:49 +01:00
if ( ! ( expected is string ) & & expected is IEnumerable enumerable )
2020-10-09 11:37:25 +02:00
{
// sort property collection by alias, not by property ids
// on members, built-in properties don't have ids (always zero)
if ( expected is PropertyCollection )
2020-12-23 11:35:49 +01:00
{
sorter = e = > ( ( PropertyCollection ) e ) . OrderBy ( x = > x . Alias ) ;
}
2020-10-09 11:37:25 +02:00
// compare lists
2020-12-23 11:35:49 +01:00
AssertListsAreEqual ( property , ( IEnumerable ) actual , enumerable , sorter , dateDeltaMilliseconds ) ;
2020-10-09 11:37:25 +02:00
}
else if ( expected is DateTime expectedDateTime )
{
// compare date & time with delta
2020-12-23 11:35:49 +01:00
var actualDateTime = ( DateTime ) actual ;
double delta = ( actualDateTime - expectedDateTime ) . TotalMilliseconds ;
2020-10-09 11:37:25 +02:00
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
2020-12-23 11:35:49 +01:00
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 ( ) ;
2020-10-09 11:37:25 +02:00
if ( expectedPropertyValues . Length ! = actualPropertyValues . Length )
2020-12-23 11:35:49 +01:00
{
2020-10-09 11:37:25 +02:00
Assert . Fail ( $"{property.DeclaringType.Name}.{property.Name}: Expected {expectedPropertyValues.Length} but got {actualPropertyValues.Length}." ) ;
2020-12-23 11:35:49 +01:00
}
for ( int i = 0 ; i < expectedPropertyValues . Length ; i + + )
2020-10-09 11:37:25 +02:00
{
2020-10-21 13:28:13 +02:00
// 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 )
{
2020-12-23 11:35:49 +01:00
var actualEditDateTime = ( DateTime ) actualPropertyValues [ i ] . EditedValue ;
2020-10-21 13:28:13 +02:00
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 )
{
2020-12-23 11:35:49 +01:00
var actualPublishedDateTime = ( DateTime ) actualPropertyValues [ i ] . PublishedValue ;
2020-10-21 13:28:13 +02:00
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}\"." ) ;
}
2020-10-09 11:37:25 +02:00
}
}
else if ( expected is IDataEditor expectedEditor )
{
Assert . IsInstanceOf < IDataEditor > ( actual ) ;
2020-12-23 11:35:49 +01:00
var actualEditor = ( IDataEditor ) actual ;
2020-10-09 11:37:25 +02:00
Assert . AreEqual ( expectedEditor . Alias , actualEditor . Alias ) ;
2020-12-23 11:35:49 +01:00
2020-10-09 11:37:25 +02:00
// what else shall we test?
}
else
{
// directly compare values
2020-12-23 11:35:49 +01:00
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>" ) ;
2020-10-09 11:37:25 +02:00
}
}
2020-10-21 13:28:13 +02:00
2020-12-23 11:35:49 +01:00
private static void AssertDateTime ( DateTime expected , DateTime actual , string failureMessage , int dateDeltaMiliseconds = 0 )
2020-10-21 13:28:13 +02:00
{
2020-12-23 11:35:49 +01:00
double delta = ( actual - expected ) . TotalMilliseconds ;
2020-10-21 13:28:13 +02:00
Assert . IsTrue ( Math . Abs ( delta ) < = dateDeltaMiliseconds , failureMessage ) ;
}
2020-10-26 10:47:14 +00:00
public void DeleteDirectory ( string path )
{
Try ( ( ) = >
{
2020-12-23 11:35:49 +01:00
if ( Directory . Exists ( path ) = = false )
{
return ;
}
foreach ( string file in Directory . EnumerateFiles ( path , "*" , SearchOption . AllDirectories ) )
{
2020-10-26 10:47:14 +00:00
File . Delete ( file ) ;
2020-12-23 11:35:49 +01:00
}
2020-10-26 10:47:14 +00:00
} ) ;
Try ( ( ) = >
{
2020-12-23 11:35:49 +01:00
if ( Directory . Exists ( path ) = = false )
{
return ;
}
2020-10-26 10:47:14 +00:00
Directory . Delete ( path , true ) ;
} ) ;
}
2020-12-23 11:35:49 +01:00
public static void TryAssert ( Action action , int maxTries = 5 , int waitMilliseconds = 200 ) = >
2020-10-26 10:47:14 +00:00
Try < AssertionException > ( action , maxTries , waitMilliseconds ) ;
2020-12-23 11:35:49 +01:00
public static void Try ( Action action , int maxTries = 5 , int waitMilliseconds = 200 ) = >
2020-10-26 10:47:14 +00:00
Try < Exception > ( action , maxTries , waitMilliseconds ) ;
public static void Try < T > ( Action action , int maxTries = 5 , int waitMilliseconds = 200 )
where T : Exception
{
2020-12-23 11:35:49 +01:00
int tries = 0 ;
2020-10-26 10:47:14 +00:00
while ( true )
{
try
{
action ( ) ;
break ;
}
catch ( T )
{
if ( tries + + > maxTries )
2020-12-23 11:35:49 +01:00
{
2020-10-26 10:47:14 +00:00
throw ;
2020-12-23 11:35:49 +01:00
}
2020-10-26 10:47:14 +00:00
Thread . Sleep ( waitMilliseconds ) ;
}
}
}
2020-03-13 14:43:41 +11:00
}
}