V10: fix build warnings in test projects (#12509)

* Run code cleanup

* Dotnet format benchmarks project

* Fix up Test.Common

* Run dotnet format + manual cleanup

* Run code cleanup for unit tests

* Run dotnet format

* Fix up errors

* Manual cleanup of Unit test project

* Update tests/Umbraco.Tests.Benchmarks/HexStringBenchmarks.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update tests/Umbraco.Tests.Integration/Testing/TestDbMeta.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update tests/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update tests/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Update tests/Umbraco.Tests.Integration/Umbraco.Core/Events/EventAggregatorTests.cs

Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>

* Fix according to review

* Fix after merge

* Fix errors

Co-authored-by: Nikolaj Geisle <niko737@edu.ucl.dk>
Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
Co-authored-by: Zeegaan <nge@umbraco.dk>
This commit is contained in:
Nikolaj Geisle
2022-06-21 08:09:38 +02:00
committed by GitHub
parent 29961d40a3
commit 7aeb400fce
599 changed files with 87303 additions and 86123 deletions

View File

@@ -9,139 +9,130 @@ using System.Data.Common;
using System.Diagnostics;
using System.Threading;
using Microsoft.Extensions.Logging;
using Moq;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Infrastructure.Migrations.Install;
using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Cms.Tests.Integration.Testing
namespace Umbraco.Cms.Tests.Integration.Testing;
public abstract class BaseTestDatabase
{
public abstract class BaseTestDatabase
protected IUmbracoDatabaseFactory _databaseFactory;
protected ILoggerFactory _loggerFactory;
protected BlockingCollection<TestDbMeta> _prepareQueue;
protected BlockingCollection<TestDbMeta> _readyEmptyQueue;
protected BlockingCollection<TestDbMeta> _readySchemaQueue;
protected IList<TestDbMeta> _testDatabases;
public BaseTestDatabase() => Instance = this;
public static BaseTestDatabase Instance { get; private set; }
public static bool IsSqlite() => Instance is SqliteTestDatabase;
public static bool IsSqlServer() => Instance is SqlServerBaseTestDatabase;
protected abstract void Initialize();
public virtual TestDbMeta AttachEmpty()
{
public static bool IsSqlite() => BaseTestDatabase.Instance is SqliteTestDatabase;
public static bool IsSqlServer() => BaseTestDatabase.Instance is SqlServerBaseTestDatabase;
protected ILoggerFactory _loggerFactory;
protected IUmbracoDatabaseFactory _databaseFactory;
protected IList<TestDbMeta> _testDatabases;
protected BlockingCollection<TestDbMeta> _prepareQueue;
protected BlockingCollection<TestDbMeta> _readySchemaQueue;
protected BlockingCollection<TestDbMeta> _readyEmptyQueue;
public static BaseTestDatabase Instance { get; private set; }
public BaseTestDatabase() => Instance = this;
protected abstract void Initialize();
public virtual TestDbMeta AttachEmpty()
if (_prepareQueue == null)
{
if (_prepareQueue == null)
{
Initialize();
}
return _readyEmptyQueue.Take();
Initialize();
}
public virtual TestDbMeta AttachSchema()
{
if (_prepareQueue == null)
{
Initialize();
}
return _readyEmptyQueue.Take();
}
return _readySchemaQueue.Take();
public virtual TestDbMeta AttachSchema()
{
if (_prepareQueue == null)
{
Initialize();
}
public virtual void Detach(TestDbMeta meta)
{
_prepareQueue.TryAdd(meta);
}
return _readySchemaQueue.Take();
}
protected virtual void PrepareDatabase() =>
Retry(10, () =>
{
while (_prepareQueue.IsCompleted == false)
{
TestDbMeta meta;
try
{
meta = _prepareQueue.Take();
}
catch (InvalidOperationException)
{
continue;
}
ResetTestDatabase(meta);
if (!meta.IsEmpty)
{
using (var conn = GetConnection(meta))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
RebuildSchema(cmd, meta);
}
}
_readySchemaQueue.TryAdd(meta);
}
else
{
_readyEmptyQueue.TryAdd(meta);
}
}
});
protected static void AddParameter(IDbCommand cmd, UmbracoDatabase.ParameterInfo parameterInfo)
{
IDbDataParameter p = cmd.CreateParameter();
p.ParameterName = parameterInfo.Name;
p.Value = parameterInfo.Value;
p.DbType = parameterInfo.DbType;
p.Size = parameterInfo.Size;
cmd.Parameters.Add(p);
}
protected abstract DbConnection GetConnection(TestDbMeta meta);
protected abstract void RebuildSchema(IDbCommand command, TestDbMeta meta);
protected abstract void ResetTestDatabase(TestDbMeta meta);
protected static void Retry(int maxIterations, Action action)
{
for (int i = 0; i < maxIterations; i++)
public virtual void Detach(TestDbMeta meta) => _prepareQueue.TryAdd(meta);
protected virtual void PrepareDatabase() =>
Retry(10, () =>
{
while (_prepareQueue.IsCompleted == false)
{
TestDbMeta meta;
try
{
action();
return;
}
catch (DbException ex)
{
// Console.Error.WriteLine($"SqlException occured, but we try again {i+1}/{maxIterations}.\n{e}");
// This can occur when there's a transaction deadlock which means (i think) that the database is still in use and hasn't been closed properly yet
// so we need to just wait a little bit
Thread.Sleep(100 * i);
if (i == maxIterations - 1)
{
Debugger.Launch();
throw;
}
meta = _prepareQueue.Take();
}
catch (InvalidOperationException)
{
// Ignore
continue;
}
ResetTestDatabase(meta);
if (!meta.IsEmpty)
{
using (var conn = GetConnection(meta))
{
conn.Open();
using (var cmd = conn.CreateCommand())
{
RebuildSchema(cmd, meta);
}
}
_readySchemaQueue.TryAdd(meta);
}
else
{
_readyEmptyQueue.TryAdd(meta);
}
}
}
});
public abstract void TearDown();
protected static void AddParameter(IDbCommand cmd, UmbracoDatabase.ParameterInfo parameterInfo)
{
var p = cmd.CreateParameter();
p.ParameterName = parameterInfo.Name;
p.Value = parameterInfo.Value;
p.DbType = parameterInfo.DbType;
p.Size = parameterInfo.Size;
cmd.Parameters.Add(p);
}
protected abstract DbConnection GetConnection(TestDbMeta meta);
protected abstract void RebuildSchema(IDbCommand command, TestDbMeta meta);
protected abstract void ResetTestDatabase(TestDbMeta meta);
protected static void Retry(int maxIterations, Action action)
{
for (var i = 0; i < maxIterations; i++)
{
try
{
action();
return;
}
catch (DbException ex)
{
// Console.Error.WriteLine($"SqlException occured, but we try again {i+1}/{maxIterations}.\n{e}");
// This can occur when there's a transaction deadlock which means (i think) that the database is still in use and hasn't been closed properly yet
// so we need to just wait a little bit
Thread.Sleep(100 * i);
if (i == maxIterations - 1)
{
Debugger.Launch();
throw;
}
}
catch (InvalidOperationException)
{
// Ignore
}
}
}
public abstract void TearDown();
}

View File

@@ -1,14 +1,13 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
namespace Umbraco.Cms.Tests.Integration.Testing
namespace Umbraco.Cms.Tests.Integration.Testing;
public interface ITestDatabase
{
public interface ITestDatabase
{
TestDbMeta AttachEmpty();
TestDbMeta AttachEmpty();
TestDbMeta AttachSchema();
TestDbMeta AttachSchema();
void Detach(TestDbMeta id);
}
void Detach(TestDbMeta id);
}

View File

@@ -5,34 +5,33 @@ using Examine;
using Examine.Lucene.Providers;
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Tests.Integration.Testing
namespace Umbraco.Cms.Tests.Integration.Testing;
/// <summary>
/// A component to customize some services to work nicely with integration tests
/// </summary>
public class IntegrationTestComponent : IComponent
{
/// <summary>
/// A component to customize some services to work nicely with integration tests
/// </summary>
public class IntegrationTestComponent : IComponent
private readonly IExamineManager _examineManager;
public IntegrationTestComponent(IExamineManager examineManager) => _examineManager = examineManager;
public void Initialize() => ConfigureExamineIndexes();
public void Terminate()
{
private readonly IExamineManager _examineManager;
}
public IntegrationTestComponent(IExamineManager examineManager) => _examineManager = examineManager;
public void Initialize() => ConfigureExamineIndexes();
public void Terminate()
/// <summary>
/// Configure all indexes to run sync (non-backbround threads) and to use RAMDirectory
/// </summary>
private void ConfigureExamineIndexes()
{
foreach (var index in _examineManager.Indexes)
{
}
/// <summary>
/// Configure all indexes to run sync (non-backbround threads) and to use RAMDirectory
/// </summary>
private void ConfigureExamineIndexes()
{
foreach (IIndex index in _examineManager.Indexes)
if (index is LuceneIndex luceneIndex)
{
if (index is LuceneIndex luceneIndex)
{
luceneIndex.WithThreadingMode(IndexThreadingMode.Synchronous);
}
luceneIndex.WithThreadingMode(IndexThreadingMode.Synchronous);
}
}
}

View File

@@ -10,136 +10,135 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Cms.Tests.Integration.Testing
namespace Umbraco.Cms.Tests.Integration.Testing;
/// <summary>
/// Manages a pool of LocalDb databases for integration testing
/// </summary>
public class LocalDbTestDatabase : SqlServerBaseTestDatabase, ITestDatabase
{
/// <summary>
/// Manages a pool of LocalDb databases for integration testing
/// </summary>
public class LocalDbTestDatabase : SqlServerBaseTestDatabase, ITestDatabase
public const string InstanceName = "UmbracoTests";
public const string DatabaseName = "UmbracoTests";
private static LocalDb.Instance s_localDbInstance;
private static string s_filesPath;
private readonly LocalDb _localDb;
private readonly TestDatabaseSettings _settings;
// It's internal because `Umbraco.Core.Persistence.LocalDb` is internal
internal LocalDbTestDatabase(TestDatabaseSettings settings, ILoggerFactory loggerFactory, LocalDb localDb, IUmbracoDatabaseFactory dbFactory)
{
public const string InstanceName = "UmbracoTests";
public const string DatabaseName = "UmbracoTests";
_loggerFactory = loggerFactory;
_databaseFactory = dbFactory;
private readonly TestDatabaseSettings _settings;
private readonly LocalDb _localDb;
private static LocalDb.Instance s_localDbInstance;
private static string s_filesPath;
_settings = settings;
_localDb = localDb;
s_filesPath = settings.FilesPath;
// It's internal because `Umbraco.Core.Persistence.LocalDb` is internal
internal LocalDbTestDatabase(TestDatabaseSettings settings, ILoggerFactory loggerFactory, LocalDb localDb, IUmbracoDatabaseFactory dbFactory)
var counter = 0;
var schema = Enumerable.Range(0, _settings.SchemaDatabaseCount)
.Select(x => TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-{++counter}", false));
var empty = Enumerable.Range(0, _settings.EmptyDatabasesCount)
.Select(x => TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-{++counter}", true));
_testDatabases = schema.Concat(empty).ToList();
s_localDbInstance = _localDb.GetInstance(InstanceName);
if (s_localDbInstance != null)
{
_loggerFactory = loggerFactory;
_databaseFactory = dbFactory;
_settings = settings;
_localDb = localDb;
s_filesPath = settings.FilesPath;
var counter = 0;
var schema = Enumerable.Range(0, _settings.SchemaDatabaseCount)
.Select(x => TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-{++counter}", false));
var empty = Enumerable.Range(0, _settings.EmptyDatabasesCount)
.Select(x => TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-{++counter}", true));
_testDatabases = schema.Concat(empty).ToList();
s_localDbInstance = _localDb.GetInstance(InstanceName);
if (s_localDbInstance != null)
{
return;
}
if (_localDb.CreateInstance(InstanceName) == false)
{
throw new Exception("Failed to create a LocalDb instance.");
}
s_localDbInstance = _localDb.GetInstance(InstanceName);
return;
}
protected override void Initialize()
if (_localDb.CreateInstance(InstanceName) == false)
{
string tempName = Guid.NewGuid().ToString("N");
s_localDbInstance.CreateDatabase(tempName, s_filesPath);
s_localDbInstance.DetachDatabase(tempName);
_prepareQueue = new BlockingCollection<TestDbMeta>();
_readySchemaQueue = new BlockingCollection<TestDbMeta>();
_readyEmptyQueue = new BlockingCollection<TestDbMeta>();
for (int i = 0; i < _testDatabases.Count; i++)
{
TestDbMeta meta = _testDatabases[i];
bool isLast = i == _testDatabases.Count - 1;
_localDb.CopyDatabaseFiles(tempName, s_filesPath, targetDatabaseName: meta.Name, overwrite: true, delete: isLast);
meta.ConnectionString = s_localDbInstance.GetAttachedConnectionString(meta.Name, s_filesPath);
_prepareQueue.Add(meta);
}
for (int i = 0; i < _settings.PrepareThreadCount; i++)
{
var thread = new Thread(PrepareDatabase);
thread.Start();
}
throw new Exception("Failed to create a LocalDb instance.");
}
public override void TearDown()
s_localDbInstance = _localDb.GetInstance(InstanceName);
}
protected override void Initialize()
{
var tempName = Guid.NewGuid().ToString("N");
s_localDbInstance.CreateDatabase(tempName, s_filesPath);
s_localDbInstance.DetachDatabase(tempName);
_prepareQueue = new BlockingCollection<TestDbMeta>();
_readySchemaQueue = new BlockingCollection<TestDbMeta>();
_readyEmptyQueue = new BlockingCollection<TestDbMeta>();
for (var i = 0; i < _testDatabases.Count; i++)
{
if (_prepareQueue == null)
var meta = _testDatabases[i];
var isLast = i == _testDatabases.Count - 1;
_localDb.CopyDatabaseFiles(tempName, s_filesPath, meta.Name, overwrite: true, delete: isLast);
meta.ConnectionString = s_localDbInstance.GetAttachedConnectionString(meta.Name, s_filesPath);
_prepareQueue.Add(meta);
}
for (var i = 0; i < _settings.PrepareThreadCount; i++)
{
var thread = new Thread(PrepareDatabase);
thread.Start();
}
}
public override void TearDown()
{
if (_prepareQueue == null)
{
return;
}
_prepareQueue.CompleteAdding();
while (_prepareQueue.TryTake(out _))
{
}
_readyEmptyQueue.CompleteAdding();
while (_readyEmptyQueue.TryTake(out _))
{
}
_readySchemaQueue.CompleteAdding();
while (_readySchemaQueue.TryTake(out _))
{
}
if (s_filesPath == null)
{
return;
}
var filename = Path.Combine(s_filesPath, DatabaseName).ToUpper();
Parallel.ForEach(s_localDbInstance.GetDatabases(), instance =>
{
if (instance.StartsWith(filename))
{
return;
s_localDbInstance.DropDatabase(instance);
}
});
_localDb.StopInstance(InstanceName);
foreach (var file in Directory.EnumerateFiles(s_filesPath))
{
if (file.EndsWith(".mdf") == false && file.EndsWith(".ldf") == false)
{
continue;
}
_prepareQueue.CompleteAdding();
while (_prepareQueue.TryTake(out _))
try
{
File.Delete(file);
}
_readyEmptyQueue.CompleteAdding();
while (_readyEmptyQueue.TryTake(out _))
catch (IOException)
{
}
_readySchemaQueue.CompleteAdding();
while (_readySchemaQueue.TryTake(out _))
{
}
if (s_filesPath == null)
{
return;
}
string filename = Path.Combine(s_filesPath, DatabaseName).ToUpper();
Parallel.ForEach(s_localDbInstance.GetDatabases(), instance =>
{
if (instance.StartsWith(filename))
{
s_localDbInstance.DropDatabase(instance);
}
});
_localDb.StopInstance(InstanceName);
foreach (string file in Directory.EnumerateFiles(s_filesPath))
{
if (file.EndsWith(".mdf") == false && file.EndsWith(".ldf") == false)
{
continue;
}
try
{
File.Delete(file);
}
catch (IOException)
{
// ignore, must still be in use but nothing we can do
}
// ignore, must still be in use but nothing we can do
}
}
}

View File

@@ -16,7 +16,6 @@ namespace Umbraco.Cms.Tests.Integration.Testing;
public abstract class SqlServerBaseTestDatabase : BaseTestDatabase
{
protected UmbracoDatabase.CommandInfo[] _cachedDatabaseInitCommands = new UmbracoDatabase.CommandInfo[0];
protected override void ResetTestDatabase(TestDbMeta meta)
@@ -58,14 +57,13 @@ public abstract class SqlServerBaseTestDatabase : BaseTestDatabase
command.CommandText = sql;
command.Parameters.Clear();
for (int i = 0; i < args.Length; i++)
for (var i = 0; i < args.Length; i++)
{
command.Parameters.AddWithValue("@" + i, args[i]);
}
}
protected override DbConnection GetConnection(TestDbMeta meta) => new SqlConnection(meta.ConnectionString);
protected override void RebuildSchema(IDbCommand command, TestDbMeta meta)
@@ -79,12 +77,12 @@ public abstract class SqlServerBaseTestDatabase : BaseTestDatabase
}
}
foreach (UmbracoDatabase.CommandInfo dbCommand in _cachedDatabaseInitCommands)
foreach (var dbCommand in _cachedDatabaseInitCommands)
{
command.CommandText = dbCommand.Text;
command.Parameters.Clear();
foreach (UmbracoDatabase.ParameterInfo parameterInfo in dbCommand.Parameters)
foreach (var parameterInfo in dbCommand.Parameters)
{
AddParameter(command, parameterInfo);
}
@@ -101,13 +99,16 @@ public abstract class SqlServerBaseTestDatabase : BaseTestDatabase
{
database.LogCommands = true;
using (NPoco.ITransaction transaction = database.GetTransaction())
using (var transaction = database.GetTransaction())
{
var options = new TestOptionsMonitor<InstallDefaultDataSettings>(new InstallDefaultDataSettings { InstallData = InstallDefaultDataOption.All });
var options =
new TestOptionsMonitor<InstallDefaultDataSettings>(
new InstallDefaultDataSettings { InstallData = InstallDefaultDataOption.All });
var schemaCreator = new DatabaseSchemaCreator(
database,
_loggerFactory.CreateLogger<DatabaseSchemaCreator>(), _loggerFactory,
_loggerFactory.CreateLogger<DatabaseSchemaCreator>(),
_loggerFactory,
new UmbracoVersion(),
Mock.Of<IEventAggregator>(),
options);

View File

@@ -11,121 +11,117 @@ using Microsoft.Extensions.Logging;
using Umbraco.Cms.Infrastructure.Persistence;
// ReSharper disable ConvertToUsingDeclaration
namespace Umbraco.Cms.Tests.Integration.Testing
namespace Umbraco.Cms.Tests.Integration.Testing;
/// <remarks>
/// It's not meant to be pretty, rushed port of LocalDb.cs + LocalDbTestDatabase.cs
/// </remarks>
public class SqlServerTestDatabase : SqlServerBaseTestDatabase, ITestDatabase
{
/// <remarks>
/// It's not meant to be pretty, rushed port of LocalDb.cs + LocalDbTestDatabase.cs
/// </remarks>
public class SqlServerTestDatabase : SqlServerBaseTestDatabase, ITestDatabase
public const string DatabaseName = "UmbracoTests";
private readonly TestDatabaseSettings _settings;
public SqlServerTestDatabase(TestDatabaseSettings settings, ILoggerFactory loggerFactory, IUmbracoDatabaseFactory databaseFactory)
{
private readonly TestDatabaseSettings _settings;
public const string DatabaseName = "UmbracoTests";
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
_databaseFactory = databaseFactory ?? throw new ArgumentNullException(nameof(databaseFactory));
public SqlServerTestDatabase(TestDatabaseSettings settings, ILoggerFactory loggerFactory,
IUmbracoDatabaseFactory databaseFactory)
_settings = settings;
var counter = 0;
var schema = Enumerable.Range(0, _settings.SchemaDatabaseCount)
.Select(x => TestDbMeta.CreateWithMasterConnectionString($"{DatabaseName}-{++counter}", false, _settings.SQLServerMasterConnectionString));
var empty = Enumerable.Range(0, _settings.EmptyDatabasesCount)
.Select(x => TestDbMeta.CreateWithMasterConnectionString($"{DatabaseName}-{++counter}", true, _settings.SQLServerMasterConnectionString));
_testDatabases = schema.Concat(empty).ToList();
}
protected override void Initialize()
{
_prepareQueue = new BlockingCollection<TestDbMeta>();
_readySchemaQueue = new BlockingCollection<TestDbMeta>();
_readyEmptyQueue = new BlockingCollection<TestDbMeta>();
foreach (var meta in _testDatabases)
{
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
_databaseFactory = databaseFactory ?? throw new ArgumentNullException(nameof(databaseFactory));
_settings = settings;
var counter = 0;
var schema = Enumerable.Range(0, _settings.SchemaDatabaseCount)
.Select(x => TestDbMeta.CreateWithMasterConnectionString($"{DatabaseName}-{++counter}", false,
_settings.SQLServerMasterConnectionString));
var empty = Enumerable.Range(0, _settings.EmptyDatabasesCount)
.Select(x => TestDbMeta.CreateWithMasterConnectionString($"{DatabaseName}-{++counter}", true,
_settings.SQLServerMasterConnectionString));
_testDatabases = schema.Concat(empty).ToList();
CreateDatabase(meta);
_prepareQueue.Add(meta);
}
protected override void Initialize()
for (var i = 0; i < _settings.PrepareThreadCount; i++)
{
_prepareQueue = new BlockingCollection<TestDbMeta>();
_readySchemaQueue = new BlockingCollection<TestDbMeta>();
_readyEmptyQueue = new BlockingCollection<TestDbMeta>();
var thread = new Thread(PrepareDatabase);
thread.Start();
}
}
foreach (TestDbMeta meta in _testDatabases)
{
CreateDatabase(meta);
_prepareQueue.Add(meta);
}
private void CreateDatabase(TestDbMeta meta)
{
Drop(meta);
for (int i = 0; i < _settings.PrepareThreadCount; i++)
using (var connection = new SqlConnection(_settings.SQLServerMasterConnectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
var thread = new Thread(PrepareDatabase);
thread.Start();
SetCommand(command, $@"CREATE DATABASE {LocalDb.QuotedName(meta.Name)}");
command.ExecuteNonQuery();
}
}
}
private void CreateDatabase(TestDbMeta meta)
private void Drop(TestDbMeta meta)
{
using (var connection = new SqlConnection(_settings.SQLServerMasterConnectionString))
{
Drop(meta);
using (var connection = new SqlConnection(_settings.SQLServerMasterConnectionString))
connection.Open();
using (var command = connection.CreateCommand())
{
connection.Open();
using (SqlCommand command = connection.CreateCommand())
SetCommand(command, "select count(1) from sys.databases where name = @0", meta.Name);
var records = (int)command.ExecuteScalar();
if (records == 0)
{
SetCommand(command, $@"CREATE DATABASE {LocalDb.QuotedName(meta.Name)}");
command.ExecuteNonQuery();
return;
}
}
}
private void Drop(TestDbMeta meta)
{
using (var connection = new SqlConnection(_settings.SQLServerMasterConnectionString))
{
connection.Open();
using (SqlCommand command = connection.CreateCommand())
{
SetCommand(command, "select count(1) from sys.databases where name = @0", meta.Name);
var records = (int)command.ExecuteScalar();
if (records == 0)
{
return;
}
string sql = $@"
var sql = $@"
ALTER DATABASE {LocalDb.QuotedName(meta.Name)}
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE";
SetCommand(command, sql);
command.ExecuteNonQuery();
SetCommand(command, sql);
command.ExecuteNonQuery();
SetCommand(command, $@"DROP DATABASE {LocalDb.QuotedName(meta.Name)}");
command.ExecuteNonQuery();
}
SetCommand(command, $@"DROP DATABASE {LocalDb.QuotedName(meta.Name)}");
command.ExecuteNonQuery();
}
}
public override void TearDown()
{
if (_prepareQueue == null)
{
return;
}
_prepareQueue.CompleteAdding();
while (_prepareQueue.TryTake(out _))
{
}
_readyEmptyQueue.CompleteAdding();
while (_readyEmptyQueue.TryTake(out _))
{
}
_readySchemaQueue.CompleteAdding();
while (_readySchemaQueue.TryTake(out _))
{
}
Parallel.ForEach(_testDatabases, Drop);
}
}
public override void TearDown()
{
if (_prepareQueue == null)
{
return;
}
_prepareQueue.CompleteAdding();
while (_prepareQueue.TryTake(out _))
{
}
_readyEmptyQueue.CompleteAdding();
while (_readyEmptyQueue.TryTake(out _))
{
}
_readySchemaQueue.CompleteAdding();
while (_readySchemaQueue.TryTake(out _))
{
}
Parallel.ForEach(_testDatabases, Drop);
}
}

View File

@@ -2,10 +2,8 @@ using System;
using System.Collections.Concurrent;
using System.Data;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;
using Moq;
@@ -16,22 +14,21 @@ using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Infrastructure.Migrations.Install;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
using Umbraco.Cms.Persistence.Sqlite;
using Umbraco.Cms.Persistence.Sqlite.Mappers;
using Umbraco.Cms.Persistence.Sqlite.Services;
using Umbraco.Cms.Tests.Common;
namespace Umbraco.Cms.Tests.Integration.Testing;
public class SqliteTestDatabase : BaseTestDatabase, ITestDatabase
{
private readonly TestDatabaseSettings _settings;
private readonly TestUmbracoDatabaseFactoryProvider _dbFactoryProvider;
public const string DatabaseName = "UmbracoTests";
private readonly TestUmbracoDatabaseFactoryProvider _dbFactoryProvider;
private readonly TestDatabaseSettings _settings;
protected UmbracoDatabase.CommandInfo[] _cachedDatabaseInitCommands = new UmbracoDatabase.CommandInfo[0];
public SqliteTestDatabase(TestDatabaseSettings settings, TestUmbracoDatabaseFactoryProvider dbFactoryProvider,
ILoggerFactory loggerFactory)
public SqliteTestDatabase(TestDatabaseSettings settings, TestUmbracoDatabaseFactoryProvider dbFactoryProvider, ILoggerFactory loggerFactory)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_dbFactoryProvider = dbFactoryProvider;
@@ -47,13 +44,19 @@ public class SqliteTestDatabase : BaseTestDatabase, ITestDatabase
_testDatabases = schema.Concat(empty).ToList();
}
public override void Detach(TestDbMeta meta)
{
meta.Connection.Close();
_prepareQueue.TryAdd(CreateSqLiteMeta(meta.IsEmpty));
}
protected override void Initialize()
{
_prepareQueue = new BlockingCollection<TestDbMeta>();
_readySchemaQueue = new BlockingCollection<TestDbMeta>();
_readyEmptyQueue = new BlockingCollection<TestDbMeta>();
foreach (TestDbMeta meta in _testDatabases)
foreach (var meta in _testDatabases)
{
_prepareQueue.Add(meta);
}
@@ -72,12 +75,6 @@ public class SqliteTestDatabase : BaseTestDatabase, ITestDatabase
meta.Connection.Open();
}
public override void Detach(TestDbMeta meta)
{
meta.Connection.Close();
_prepareQueue.TryAdd(CreateSqLiteMeta(meta.IsEmpty));
}
protected override DbConnection GetConnection(TestDbMeta meta) => new SqliteConnection(meta.ConnectionString);
protected override void RebuildSchema(IDbCommand command, TestDbMeta meta)
@@ -101,7 +98,7 @@ public class SqliteTestDatabase : BaseTestDatabase, ITestDatabase
database.Mappers.Add(new NullableDateMapper());
database.Mappers.Add(new SqlitePocoGuidMapper());
foreach (UmbracoDatabase.CommandInfo dbCommand in _cachedDatabaseInitCommands)
foreach (var dbCommand in _cachedDatabaseInitCommands)
{
database.Execute(dbCommand.Text, dbCommand.Parameters.Select(x => x.Value).ToArray());
}
@@ -117,9 +114,11 @@ public class SqliteTestDatabase : BaseTestDatabase, ITestDatabase
using var database = (UmbracoDatabase)dbFactory.CreateDatabase();
database.LogCommands = true;
using NPoco.ITransaction transaction = database.GetTransaction();
using var transaction = database.GetTransaction();
var options = new TestOptionsMonitor<InstallDefaultDataSettings>(new InstallDefaultDataSettings { InstallData = InstallDefaultDataOption.All });
var options =
new TestOptionsMonitor<InstallDefaultDataSettings>(
new InstallDefaultDataSettings { InstallData = InstallDefaultDataOption.All });
var schemaCreator = new DatabaseSchemaCreator(
database,
@@ -145,26 +144,29 @@ public class SqliteTestDatabase : BaseTestDatabase, ITestDatabase
}
_prepareQueue.CompleteAdding();
while (_prepareQueue.TryTake(out _)) { }
while (_prepareQueue.TryTake(out _))
{ }
_readyEmptyQueue.CompleteAdding();
while (_readyEmptyQueue.TryTake(out _)) { }
while (_readyEmptyQueue.TryTake(out _))
{ }
_readySchemaQueue.CompleteAdding();
while (_readySchemaQueue.TryTake(out _)) { }
while (_readySchemaQueue.TryTake(out _))
{ }
}
private TestDbMeta CreateSqLiteMeta(bool empty)
{
var builder = new SqliteConnectionStringBuilder()
var builder = new SqliteConnectionStringBuilder
{
DataSource = $"{Guid.NewGuid()}",
Mode = SqliteOpenMode.Memory,
ForeignKeys = true,
Pooling = false, // When pooling true, files kept open after connections closed, bad for cleanup.
Cache = SqliteCacheMode.Shared,
Cache = SqliteCacheMode.Shared
};
return new TestDbMeta(builder.DataSource, empty, builder.ConnectionString, Persistence.Sqlite.Constants.ProviderName, "InMemory");
return new TestDbMeta(builder.DataSource, empty, builder.ConnectionString, Constants.ProviderName, "InMemory");
}
}

View File

@@ -1,14 +1,12 @@
using System;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Tests.Integration.Testing
namespace Umbraco.Cms.Tests.Integration.Testing;
public class TestConflictingRouteService : IConflictingRouteService
{
public class TestConflictingRouteService : IConflictingRouteService
public bool HasConflictingRoutes(out string controllername)
{
public bool HasConflictingRoutes(out string controllername)
{
controllername = string.Empty;
return false;
}
controllername = string.Empty;
return false;
}
}

View File

@@ -2,57 +2,53 @@
// See LICENSE for more details.
using System;
using System.IO;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Infrastructure.Persistence;
namespace Umbraco.Cms.Tests.Integration.Testing
namespace Umbraco.Cms.Tests.Integration.Testing;
public static class TestDatabaseFactory
{
public static class TestDatabaseFactory
/// <summary>
/// Creates a TestDatabase instance
/// </summary>
/// <remarks>
/// SQL Server setup requires configured master connection string &amp; privileges to create database.
/// </remarks>
/// <example>
/// <code>
/// # SQL Server Environment variable setup
/// $ export Tests__Database__DatabaseType="SqlServer"
/// $ export Tests__Database__SQLServerMasterConnectionString="Server=localhost,1433; User Id=sa; Password=MySuperSecretPassword123!;"
/// </code>
/// </example>
/// <example>
/// <code>
/// # Docker cheat sheet
/// $ docker run -e 'ACCEPT_EULA=Y' -e "SA_PASSWORD=MySuperSecretPassword123!" -e 'MSSQL_PID=Developer' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-latest-ubuntu
/// </code>
/// </example>
public static ITestDatabase Create(TestDatabaseSettings settings, TestUmbracoDatabaseFactoryProvider dbFactory, ILoggerFactory loggerFactory) =>
settings.DatabaseType switch
{
TestDatabaseSettings.TestDatabaseType.Sqlite => new SqliteTestDatabase(settings, dbFactory, loggerFactory),
TestDatabaseSettings.TestDatabaseType.SqlServer => CreateSqlServer(settings, loggerFactory, dbFactory),
TestDatabaseSettings.TestDatabaseType.LocalDb => CreateLocalDb(settings, loggerFactory, dbFactory),
_ => throw new ApplicationException("Unsupported test database provider")
};
private static ITestDatabase CreateLocalDb(TestDatabaseSettings settings, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
{
/// <summary>
/// Creates a TestDatabase instance
/// </summary>
/// <remarks>
/// SQL Server setup requires configured master connection string &amp; privileges to create database.
/// </remarks>
/// <example>
/// <code>
/// # SQL Server Environment variable setup
/// $ export Tests__Database__DatabaseType="SqlServer"
/// $ export Tests__Database__SQLServerMasterConnectionString="Server=localhost,1433; User Id=sa; Password=MySuperSecretPassword123!;"
/// </code>
/// </example>
/// <example>
/// <code>
/// # Docker cheat sheet
/// $ docker run -e 'ACCEPT_EULA=Y' -e "SA_PASSWORD=MySuperSecretPassword123!" -e 'MSSQL_PID=Developer' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-latest-ubuntu
/// </code>
/// </example>
public static ITestDatabase Create(TestDatabaseSettings settings, TestUmbracoDatabaseFactoryProvider dbFactory, ILoggerFactory loggerFactory) =>
settings.DatabaseType switch
{
TestDatabaseSettings.TestDatabaseType.Sqlite=> new SqliteTestDatabase(settings, dbFactory, loggerFactory),
TestDatabaseSettings.TestDatabaseType.SqlServer => CreateSqlServer(settings, loggerFactory, dbFactory),
TestDatabaseSettings.TestDatabaseType.LocalDb => CreateLocalDb(settings, loggerFactory, dbFactory),
_ => throw new ApplicationException("Unsupported test database provider")
};
var localDb = new LocalDb();
private static ITestDatabase CreateLocalDb(TestDatabaseSettings settings, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
if (!localDb.IsAvailable)
{
var localDb = new LocalDb();
if (!localDb.IsAvailable)
{
throw new InvalidOperationException("LocalDB is not available.");
}
return new LocalDbTestDatabase(settings, loggerFactory, localDb, dbFactory.Create());
throw new InvalidOperationException("LocalDB is not available.");
}
private static ITestDatabase CreateSqlServer(TestDatabaseSettings settings, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
{
return new SqlServerTestDatabase(settings, loggerFactory, dbFactory.Create());
}
return new LocalDbTestDatabase(settings, loggerFactory, localDb, dbFactory.Create());
}
private static ITestDatabase CreateSqlServer(TestDatabaseSettings settings, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory) =>
new SqlServerTestDatabase(settings, loggerFactory, dbFactory.Create());
}

View File

@@ -1,29 +1,27 @@
namespace Umbraco.Cms.Tests.Integration.Testing;
namespace Umbraco.Cms.Tests.Integration.Testing
public class TestDatabaseSettings
{
public class TestDatabaseSettings
public enum TestDatabaseType
{
public TestDatabaseType DatabaseType { get; set; }
public int PrepareThreadCount { get; set; }
public int SchemaDatabaseCount { get; set; }
public int EmptyDatabasesCount { get; set; }
public string FilesPath { get; set; }
/// <remarks>
/// Only used for SQL Server e.g. on Linux/MacOS (not required for localdb).
/// </remarks>
public string SQLServerMasterConnectionString { get; set; }
public enum TestDatabaseType
{
Unknown,
Sqlite,
SqlServer,
LocalDb
}
Unknown,
Sqlite,
SqlServer,
LocalDb
}
public TestDatabaseType DatabaseType { get; set; }
public int PrepareThreadCount { get; set; }
public int SchemaDatabaseCount { get; set; }
public int EmptyDatabasesCount { get; set; }
public string FilesPath { get; set; }
/// <remarks>
/// Only used for SQL Server e.g. on Linux/MacOS (not required for localdb).
/// </remarks>
public string SQLServerMasterConnectionString { get; set; }
}

View File

@@ -4,47 +4,42 @@
using System.Data.Common;
using System.Text.RegularExpressions;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Persistence.SqlServer;
namespace Umbraco.Cms.Tests.Integration.Testing
namespace Umbraco.Cms.Tests.Integration.Testing;
public class TestDbMeta
{
public class TestDbMeta
public TestDbMeta(string name, bool isEmpty, string connectionString, string providerName, string path)
{
public string Name { get; }
public bool IsEmpty { get; }
public string ConnectionString { get; set; }
public string Provider { get; set; }
public string Path { get; set; } // Null if not embedded.
public DbConnection Connection { get; set; } // for SQLite in memory, can move to subclass later.
IsEmpty = isEmpty;
Name = name;
ConnectionString = connectionString;
Provider = providerName;
Path = path;
}
public TestDbMeta(string name, bool isEmpty, string connectionString, string providerName, string path)
{
IsEmpty = isEmpty;
Name = name;
ConnectionString = connectionString;
Provider = providerName;
Path = path;
}
public string Name { get; }
public bool IsEmpty { get; }
public string ConnectionString { get; set; }
public string Provider { get; set; }
public string Path { get; set; } // Null if not embedded.
public DbConnection Connection { get; set; } // for SQLite in memory, can move to subclass later.
private static string ConstructConnectionString(string masterConnectionString, string databaseName)
{
string prefix = Regex.Replace(masterConnectionString, "Database=.+?;", string.Empty);
string connectionString = $"{prefix};Database={databaseName};";
return connectionString.Replace(";;", ";");
}
private static string ConstructConnectionString(string masterConnectionString, string databaseName)
{
var prefix = Regex.Replace(masterConnectionString, "Database=.+?;", string.Empty);
var connectionString = $"{prefix};Database={databaseName};";
return connectionString.Replace(";;", ";");
}
public static TestDbMeta CreateWithMasterConnectionString(string name, bool isEmpty, string masterConnectionString) =>
new TestDbMeta(name, isEmpty, ConstructConnectionString(masterConnectionString, name), Persistence.SqlServer.Constants.ProviderName, null);
// LocalDb mdf funtimes
public static TestDbMeta CreateWithoutConnectionString(string name, bool isEmpty) =>
new TestDbMeta(name, isEmpty, null, Persistence.SqlServer.Constants.ProviderName, null);
// LocalDb mdf funtimes
public static TestDbMeta CreateWithoutConnectionString(string name, bool isEmpty) =>
new(name, isEmpty, null, Constants.ProviderName, null);
public ConnectionStrings ToStronglyTypedConnectionString() =>
new ConnectionStrings
{
Name = Name,
ConnectionString = ConnectionString,
ProviderName = Provider
};
}
public ConnectionStrings ToStronglyTypedConnectionString() =>
new() { Name = Name, ConnectionString = ConnectionString, ProviderName = Provider };
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
@@ -9,49 +8,48 @@ using Umbraco.Cms.Infrastructure.Migrations.Install;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
namespace Umbraco.Cms.Tests.Integration.Testing
namespace Umbraco.Cms.Tests.Integration.Testing;
/// <summary>
/// I want to be able to create a database for integration testsing without setting the connection string on the
/// singleton database factory forever.
/// </summary>
public class TestUmbracoDatabaseFactoryProvider
{
/// <summary>
/// I want to be able to create a database for integration testsing without setting the connection string on the
/// singleton database factory forever.
/// </summary>
public class TestUmbracoDatabaseFactoryProvider
private readonly IOptionsMonitor<ConnectionStrings> _connectionStrings;
private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory;
private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator;
private readonly IOptions<GlobalSettings> _globalSettings;
private readonly ILoggerFactory _loggerFactory;
private readonly IMapperCollection _mappers;
private readonly NPocoMapperCollection _npocoMappers;
public TestUmbracoDatabaseFactoryProvider(
ILoggerFactory loggerFactory,
IOptions<GlobalSettings> globalSettings,
IOptionsMonitor<ConnectionStrings> connectionStrings,
IMapperCollection mappers,
IDbProviderFactoryCreator dbProviderFactoryCreator,
DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory,
NPocoMapperCollection npocoMappers)
{
private readonly ILoggerFactory _loggerFactory;
private readonly IOptions<GlobalSettings> _globalSettings;
private readonly IOptionsMonitor<ConnectionStrings> _connectionStrings;
private readonly IMapperCollection _mappers;
private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator;
private readonly DatabaseSchemaCreatorFactory _databaseSchemaCreatorFactory;
private readonly NPocoMapperCollection _npocoMappers;
public TestUmbracoDatabaseFactoryProvider(
ILoggerFactory loggerFactory,
IOptions<GlobalSettings> globalSettings,
IOptionsMonitor<ConnectionStrings> connectionStrings,
IMapperCollection mappers,
IDbProviderFactoryCreator dbProviderFactoryCreator,
DatabaseSchemaCreatorFactory databaseSchemaCreatorFactory,
NPocoMapperCollection npocoMappers)
{
_loggerFactory = loggerFactory;
_globalSettings = globalSettings;
_connectionStrings = connectionStrings;
_mappers = mappers;
_dbProviderFactoryCreator = dbProviderFactoryCreator;
_databaseSchemaCreatorFactory = databaseSchemaCreatorFactory;
_npocoMappers = npocoMappers;
}
public IUmbracoDatabaseFactory Create()
=> new UmbracoDatabaseFactory(
_loggerFactory.CreateLogger<UmbracoDatabaseFactory>(),
_loggerFactory,
_globalSettings,
_connectionStrings,
_mappers,
_dbProviderFactoryCreator,
_databaseSchemaCreatorFactory,
_npocoMappers);
_loggerFactory = loggerFactory;
_globalSettings = globalSettings;
_connectionStrings = connectionStrings;
_mappers = mappers;
_dbProviderFactoryCreator = dbProviderFactoryCreator;
_databaseSchemaCreatorFactory = databaseSchemaCreatorFactory;
_npocoMappers = npocoMappers;
}
public IUmbracoDatabaseFactory Create()
=> new UmbracoDatabaseFactory(
_loggerFactory.CreateLogger<UmbracoDatabaseFactory>(),
_loggerFactory,
_globalSettings,
_connectionStrings,
_mappers,
_dbProviderFactoryCreator,
_databaseSchemaCreatorFactory,
_npocoMappers);
}

View File

@@ -1,5 +1,4 @@
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -7,13 +6,10 @@ 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.Composing;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Core.Web;
@@ -27,175 +23,174 @@ using Umbraco.Cms.Tests.Integration.DependencyInjection;
using Umbraco.Cms.Tests.Integration.Extensions;
using Umbraco.Cms.Web.Common.Hosting;
using Umbraco.Extensions;
using IScopeProvider = Umbraco.Cms.Infrastructure.Scoping.IScopeProvider;
using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Tests.Integration.Testing
namespace Umbraco.Cms.Tests.Integration.Testing;
/// <summary>
/// Abstract class for integration tests
/// </summary>
/// <remarks>
/// This will use a Host Builder to boot and install Umbraco ready for use
/// </remarks>
public abstract class UmbracoIntegrationTest : UmbracoIntegrationTestBase
{
private IHost _host;
protected IServiceProvider Services => _host.Services;
/// <summary>
/// Abstract class for integration tests
/// Gets the <see cref="IScopeProvider" />
/// </summary>
protected IScopeProvider ScopeProvider => Services.GetRequiredService<IScopeProvider>();
/// <summary>
/// Gets the <see cref="IScopeAccessor" />
/// </summary>
protected IScopeAccessor ScopeAccessor => Services.GetRequiredService<IScopeAccessor>();
/// <summary>
/// Gets the <see cref="ILoggerFactory" />
/// </summary>
protected ILoggerFactory LoggerFactory => Services.GetRequiredService<ILoggerFactory>();
protected AppCaches AppCaches => Services.GetRequiredService<AppCaches>();
protected IIOHelper IOHelper => Services.GetRequiredService<IIOHelper>();
protected IShortStringHelper ShortStringHelper => Services.GetRequiredService<IShortStringHelper>();
protected GlobalSettings GlobalSettings => Services.GetRequiredService<IOptions<GlobalSettings>>().Value;
protected IMapperCollection Mappers => Services.GetRequiredService<IMapperCollection>();
protected UserBuilder UserBuilderInstance { get; } = new();
protected UserGroupBuilder UserGroupBuilderInstance { get; } = new();
[SetUp]
public void Setup()
{
InMemoryConfiguration[Constants.Configuration.ConfigUnattended + ":" + nameof(UnattendedSettings.InstallUnattended)] = "true";
var hostBuilder = CreateHostBuilder();
_host = hostBuilder.Build();
UseTestDatabase(_host.Services);
_host.Start();
if (TestOptions.Boot)
{
Services.GetRequiredService<IUmbracoContextFactory>().EnsureUmbracoContext();
}
}
[TearDown]
public void TearDownAsync() => _host.StopAsync();
/// <summary>
/// Create the Generic Host and execute startup ConfigureServices/Configure calls
/// </summary>
private IHostBuilder CreateHostBuilder()
{
var hostBuilder = Host.CreateDefaultBuilder()
.ConfigureUmbracoDefaults()
// 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.
.ConfigureAppConfiguration((context, configBuilder) =>
{
context.HostingEnvironment = TestHelper.GetWebHostEnvironment();
configBuilder.Sources.Clear();
configBuilder.AddInMemoryCollection(InMemoryConfiguration);
configBuilder.AddConfiguration(GlobalSetupTeardown.TestConfiguration);
Configuration = configBuilder.Build();
})
.ConfigureServices((_, services) =>
{
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>());
}
});
return hostBuilder;
}
protected void ConfigureServices(IServiceCollection services)
{
services.AddTransient<TestUmbracoDatabaseFactoryProvider>();
var webHostEnvironment = TestHelper.GetWebHostEnvironment();
services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment);
// We register this service because we need it for IRuntimeState, if we don't this breaks 900 tests
services.AddSingleton<IConflictingRouteService, TestConflictingRouteService>();
services.AddLogger(webHostEnvironment, Configuration);
// Add it!
var hostingEnvironment = TestHelper.GetHostingEnvironment();
var 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()
.AddRuntimeMinifier()
.AddBackOfficeAuthentication()
.AddBackOfficeIdentity()
.AddMembersIdentity()
.AddExamine()
.AddUmbracoSqlServerSupport()
.AddUmbracoSqliteSupport()
.AddTestServices(TestHelper);
if (TestOptions.Mapper)
{
// TODO: Should these just be called from within AddUmbracoCore/AddWebComponents?
builder
.AddCoreMappingProfiles()
.AddWebMappingProfiles();
}
services.AddSignalR();
services.AddMvc();
CustomTestSetup(builder);
builder.Build();
}
/// <summary>
/// Hook for altering UmbracoBuilder setup
/// </summary>
/// <remarks>
/// This will use a Host Builder to boot and install Umbraco ready for use
/// Can also be used for registering test doubles.
/// </remarks>
public abstract class UmbracoIntegrationTest : UmbracoIntegrationTestBase
protected virtual void CustomTestSetup(IUmbracoBuilder builder)
{
private IHost _host;
protected IServiceProvider Services => _host.Services;
[SetUp]
public void Setup()
{
InMemoryConfiguration[Core.Constants.Configuration.ConfigUnattended + ":" + nameof(UnattendedSettings.InstallUnattended)] = "true";
IHostBuilder hostBuilder = CreateHostBuilder();
_host = hostBuilder.Build();
UseTestDatabase(_host.Services);
_host.Start();
if (TestOptions.Boot)
{
Services.GetRequiredService<IUmbracoContextFactory>().EnsureUmbracoContext();
}
}
[TearDown]
public void TearDownAsync() => _host.StopAsync();
/// <summary>
/// Create the Generic Host and execute startup ConfigureServices/Configure calls
/// </summary>
private IHostBuilder CreateHostBuilder()
{
IHostBuilder hostBuilder = Host.CreateDefaultBuilder()
.ConfigureUmbracoDefaults()
// 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.
.ConfigureAppConfiguration((context, configBuilder) =>
{
context.HostingEnvironment = TestHelper.GetWebHostEnvironment();
configBuilder.Sources.Clear();
configBuilder.AddInMemoryCollection(InMemoryConfiguration);
configBuilder.AddConfiguration(GlobalSetupTeardown.TestConfiguration);
Configuration = configBuilder.Build();
})
.ConfigureServices((_, services) =>
{
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>());
}
});
return hostBuilder;
}
protected void ConfigureServices(IServiceCollection services)
{
services.AddTransient<TestUmbracoDatabaseFactoryProvider>();
IWebHostEnvironment webHostEnvironment = TestHelper.GetWebHostEnvironment();
services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment);
// We register this service because we need it for IRuntimeState, if we don't this breaks 900 tests
services.AddSingleton<IConflictingRouteService, TestConflictingRouteService>();
services.AddLogger(webHostEnvironment, Configuration);
// Add it!
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()
.AddRuntimeMinifier()
.AddBackOfficeAuthentication()
.AddBackOfficeIdentity()
.AddMembersIdentity()
.AddExamine()
.AddUmbracoSqlServerSupport()
.AddUmbracoSqliteSupport()
.AddTestServices(TestHelper);
if (TestOptions.Mapper)
{
// TODO: Should these just be called from within AddUmbracoCore/AddWebComponents?
builder
.AddCoreMappingProfiles()
.AddWebMappingProfiles();
}
services.AddSignalR();
services.AddMvc();
CustomTestSetup(builder);
builder.Build();
}
/// <summary>
/// Hook for altering UmbracoBuilder setup
/// </summary>
/// <remarks>
/// Can also be used for registering test doubles.
/// </remarks>
protected virtual void CustomTestSetup(IUmbracoBuilder builder)
{
}
/// <summary>
/// Hook for registering test doubles.
/// </summary>
protected virtual void ConfigureTestServices(IServiceCollection services)
{
}
protected virtual T GetRequiredService<T>() => Services.GetRequiredService<T>();
/// <summary>
/// Gets the <see cref="IScopeProvider"/>
/// </summary>
protected IScopeProvider ScopeProvider => Services.GetRequiredService<IScopeProvider>();
/// <summary>
/// Gets the <see cref="IScopeAccessor"/>
/// </summary>
protected IScopeAccessor ScopeAccessor => Services.GetRequiredService<IScopeAccessor>();
/// <summary>
/// Gets the <see cref="ILoggerFactory"/>
/// </summary>
protected ILoggerFactory LoggerFactory => Services.GetRequiredService<ILoggerFactory>();
protected AppCaches AppCaches => Services.GetRequiredService<AppCaches>();
protected IIOHelper IOHelper => Services.GetRequiredService<IIOHelper>();
protected IShortStringHelper ShortStringHelper => Services.GetRequiredService<IShortStringHelper>();
protected GlobalSettings GlobalSettings => Services.GetRequiredService<IOptions<GlobalSettings>>().Value;
protected IMapperCollection Mappers => Services.GetRequiredService<IMapperCollection>();
protected UserBuilder UserBuilderInstance { get; } = new ();
protected UserGroupBuilder UserGroupBuilderInstance { get; } = new ();
}
/// <summary>
/// Hook for registering test doubles.
/// </summary>
protected virtual void ConfigureTestServices(IServiceCollection services)
{
}
protected virtual T GetRequiredService<T>() => Services.GetRequiredService<T>();
}

View File

@@ -1,14 +1,11 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;
using NUnit.Framework;
using Serilog;
@@ -22,29 +19,29 @@ using Umbraco.Cms.Tests.Integration.Implementations;
namespace Umbraco.Cms.Tests.Integration.Testing;
/// <summary>
/// Base class for all UmbracoIntegrationTests
/// Base class for all UmbracoIntegrationTests
/// </summary>
[SingleThreaded]
[NonParallelizable]
public abstract class UmbracoIntegrationTestBase
{
private static readonly object s_dbLocker = new ();
private static readonly object s_dbLocker = new();
private static ITestDatabase s_dbInstance;
private static TestDbMeta s_fixtureDbMeta;
private static int s_testCount = 1;
private readonly List<Action> _fixtureTeardown = new();
private readonly Queue<Action> _testTeardown = new();
private bool _firstTestInFixture = true;
private readonly Queue<Action> _testTeardown = new ();
private readonly List<Action> _fixtureTeardown = new ();
protected Dictionary<string, string> InMemoryConfiguration { get; } = new ();
protected Dictionary<string, string> InMemoryConfiguration { get; } = new();
protected IConfiguration Configuration { get; set; }
protected UmbracoTestAttribute TestOptions =>
TestOptionAttributeBase.GetTestOptions<UmbracoTestAttribute>();
protected TestHelper TestHelper { get; } = new ();
protected TestHelper TestHelper { get; } = new();
private void AddOnTestTearDown(Action tearDown) => _testTeardown.Enqueue(tearDown);
@@ -61,7 +58,7 @@ public abstract class UmbracoIntegrationTestBase
[OneTimeTearDown]
public void FixtureTearDown()
{
foreach (Action a in _fixtureTeardown)
foreach (var a in _fixtureTeardown)
{
a();
}
@@ -72,7 +69,7 @@ public abstract class UmbracoIntegrationTestBase
{
_firstTestInFixture = false;
while (_testTeardown.TryDequeue(out Action a))
while (_testTeardown.TryDequeue(out var a))
{
a();
}
@@ -120,11 +117,11 @@ public abstract class UmbracoIntegrationTestBase
protected void UseTestDatabase(IServiceProvider serviceProvider)
{
IRuntimeState state = serviceProvider.GetRequiredService<IRuntimeState>();
TestUmbracoDatabaseFactoryProvider testDatabaseFactoryProvider = serviceProvider.GetRequiredService<TestUmbracoDatabaseFactoryProvider>();
IUmbracoDatabaseFactory databaseFactory = serviceProvider.GetRequiredService<IUmbracoDatabaseFactory>();
ILoggerFactory loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
IOptionsMonitor<ConnectionStrings> connectionStrings = serviceProvider.GetRequiredService<IOptionsMonitor<ConnectionStrings>>();
var state = serviceProvider.GetRequiredService<IRuntimeState>();
var testDatabaseFactoryProvider = serviceProvider.GetRequiredService<TestUmbracoDatabaseFactoryProvider>();
var databaseFactory = serviceProvider.GetRequiredService<IUmbracoDatabaseFactory>();
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
var connectionStrings = serviceProvider.GetRequiredService<IOptionsMonitor<ConnectionStrings>>();
// This will create a db, install the schema and ensure the app is configured to run
SetupTestDatabase(testDatabaseFactoryProvider, connectionStrings, databaseFactory, loggerFactory, state);
@@ -146,87 +143,86 @@ public abstract class UmbracoIntegrationTestBase
}
private void SetupTestDatabase(
TestUmbracoDatabaseFactoryProvider testUmbracoDatabaseFactoryProvider,
IOptionsMonitor<ConnectionStrings> connectionStrings,
IUmbracoDatabaseFactory databaseFactory,
ILoggerFactory loggerFactory,
IRuntimeState runtimeState)
TestUmbracoDatabaseFactoryProvider testUmbracoDatabaseFactoryProvider,
IOptionsMonitor<ConnectionStrings> connectionStrings,
IUmbracoDatabaseFactory databaseFactory,
ILoggerFactory loggerFactory,
IRuntimeState runtimeState)
{
if (TestOptions.Database == UmbracoTestOptions.Database.None)
{
if (TestOptions.Database == UmbracoTestOptions.Database.None)
{
return;
}
ITestDatabase db = GetOrCreateDatabase(loggerFactory, testUmbracoDatabaseFactoryProvider);
switch (TestOptions.Database)
{
case UmbracoTestOptions.Database.NewSchemaPerTest:
// New DB + Schema
TestDbMeta newSchemaDbMeta = db.AttachSchema();
// Add teardown callback
AddOnTestTearDown(() => db.Detach(newSchemaDbMeta));
ConfigureTestDatabaseFactory(newSchemaDbMeta, databaseFactory, runtimeState, connectionStrings);
Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level);
break;
case UmbracoTestOptions.Database.NewEmptyPerTest:
TestDbMeta newEmptyDbMeta = db.AttachEmpty();
// Add teardown callback
AddOnTestTearDown(() => db.Detach(newEmptyDbMeta));
ConfigureTestDatabaseFactory(newEmptyDbMeta, databaseFactory, runtimeState, connectionStrings);
Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level);
break;
case UmbracoTestOptions.Database.NewSchemaPerFixture:
// Only attach schema once per fixture
// Doing it more than once will block the process since the old db hasn't been detached
// and it would be the same as NewSchemaPerTest even if it didn't block
if (_firstTestInFixture)
{
// New DB + Schema
TestDbMeta newSchemaFixtureDbMeta = db.AttachSchema();
s_fixtureDbMeta = newSchemaFixtureDbMeta;
// Add teardown callback
AddOnFixtureTearDown(() => db.Detach(newSchemaFixtureDbMeta));
}
ConfigureTestDatabaseFactory(s_fixtureDbMeta, databaseFactory, runtimeState, connectionStrings);
break;
case UmbracoTestOptions.Database.NewEmptyPerFixture:
// Only attach schema once per fixture
// Doing it more than once will block the process since the old db hasn't been detached
// and it would be the same as NewSchemaPerTest even if it didn't block
if (_firstTestInFixture)
{
// New DB + Schema
TestDbMeta newEmptyFixtureDbMeta = db.AttachEmpty();
s_fixtureDbMeta = newEmptyFixtureDbMeta;
// Add teardown callback
AddOnFixtureTearDown(() => db.Detach(newEmptyFixtureDbMeta));
}
ConfigureTestDatabaseFactory(s_fixtureDbMeta, databaseFactory, runtimeState, connectionStrings);
break;
default:
throw new ArgumentOutOfRangeException(nameof(TestOptions), TestOptions, null);
}
return;
}
var db = GetOrCreateDatabase(loggerFactory, testUmbracoDatabaseFactoryProvider);
private ITestDatabase GetOrCreateDatabase(ILoggerFactory loggerFactory,
TestUmbracoDatabaseFactoryProvider dbFactory)
switch (TestOptions.Database)
{
case UmbracoTestOptions.Database.NewSchemaPerTest:
// New DB + Schema
var newSchemaDbMeta = db.AttachSchema();
// Add teardown callback
AddOnTestTearDown(() => db.Detach(newSchemaDbMeta));
ConfigureTestDatabaseFactory(newSchemaDbMeta, databaseFactory, runtimeState, connectionStrings);
Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level);
break;
case UmbracoTestOptions.Database.NewEmptyPerTest:
var newEmptyDbMeta = db.AttachEmpty();
// Add teardown callback
AddOnTestTearDown(() => db.Detach(newEmptyDbMeta));
ConfigureTestDatabaseFactory(newEmptyDbMeta, databaseFactory, runtimeState, connectionStrings);
Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level);
break;
case UmbracoTestOptions.Database.NewSchemaPerFixture:
// Only attach schema once per fixture
// Doing it more than once will block the process since the old db hasn't been detached
// and it would be the same as NewSchemaPerTest even if it didn't block
if (_firstTestInFixture)
{
// New DB + Schema
var newSchemaFixtureDbMeta = db.AttachSchema();
s_fixtureDbMeta = newSchemaFixtureDbMeta;
// Add teardown callback
AddOnFixtureTearDown(() => db.Detach(newSchemaFixtureDbMeta));
}
ConfigureTestDatabaseFactory(s_fixtureDbMeta, databaseFactory, runtimeState, connectionStrings);
break;
case UmbracoTestOptions.Database.NewEmptyPerFixture:
// Only attach schema once per fixture
// Doing it more than once will block the process since the old db hasn't been detached
// and it would be the same as NewSchemaPerTest even if it didn't block
if (_firstTestInFixture)
{
// New DB + Schema
var newEmptyFixtureDbMeta = db.AttachEmpty();
s_fixtureDbMeta = newEmptyFixtureDbMeta;
// Add teardown callback
AddOnFixtureTearDown(() => db.Detach(newEmptyFixtureDbMeta));
}
ConfigureTestDatabaseFactory(s_fixtureDbMeta, databaseFactory, runtimeState, connectionStrings);
break;
default:
throw new ArgumentOutOfRangeException(nameof(TestOptions), TestOptions, null);
}
}
private ITestDatabase GetOrCreateDatabase(ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
{
lock (s_dbLocker)
{
@@ -238,11 +234,13 @@ public abstract class UmbracoIntegrationTestBase
var settings = new TestDatabaseSettings
{
FilesPath = Path.Combine(TestHelper.WorkingDirectory, "databases"),
DatabaseType = Configuration.GetValue<TestDatabaseSettings.TestDatabaseType>("Tests:Database:DatabaseType"),
DatabaseType =
Configuration.GetValue<TestDatabaseSettings.TestDatabaseType>("Tests:Database:DatabaseType"),
PrepareThreadCount = Configuration.GetValue<int>("Tests:Database:PrepareThreadCount"),
EmptyDatabasesCount = Configuration.GetValue<int>("Tests:Database:EmptyDatabasesCount"),
SchemaDatabaseCount = Configuration.GetValue<int>("Tests:Database:SchemaDatabaseCount"),
SQLServerMasterConnectionString = Configuration.GetValue<string>("Tests:Database:SQLServerMasterConnectionString"),
SQLServerMasterConnectionString =
Configuration.GetValue<string>("Tests:Database:SQLServerMasterConnectionString")
};
Directory.CreateDirectory(settings.FilesPath);

View File

@@ -5,66 +5,65 @@ using System;
using NUnit.Framework;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Implement;
using Umbraco.Cms.Tests.Common.Builders;
namespace Umbraco.Cms.Tests.Integration.Testing
namespace Umbraco.Cms.Tests.Integration.Testing;
public abstract class UmbracoIntegrationTestWithContent : UmbracoIntegrationTest
{
public abstract class UmbracoIntegrationTestWithContent : UmbracoIntegrationTest
protected IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();
protected IFileService FileService => GetRequiredService<IFileService>();
protected ContentService ContentService => (ContentService)GetRequiredService<IContentService>();
protected Content Trashed { get; private set; }
protected Content Subpage2 { get; private set; }
protected Content Subpage3 { get; private set; }
protected Content Subpage { get; private set; }
protected Content Textpage { get; private set; }
protected ContentType ContentType { get; private set; }
[SetUp]
public void Setup() => CreateTestData();
public virtual void CreateTestData()
{
protected IContentTypeService ContentTypeService => GetRequiredService<IContentTypeService>();
// NOTE Maybe not the best way to create/save test data as we are using the services, which are being tested.
var template = TemplateBuilder.CreateTextPageTemplate();
FileService.SaveTemplate(template);
protected IFileService FileService => GetRequiredService<IFileService>();
// Create and Save ContentType "umbTextpage" -> 1051 (template), 1052 (content type)
ContentType =
ContentTypeBuilder.CreateSimpleContentType("umbTextpage", "Textpage", defaultTemplateId: template.Id);
ContentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522");
ContentTypeService.Save(ContentType);
protected ContentService ContentService => (ContentService)GetRequiredService<IContentService>();
// Create and Save Content "Homepage" based on "umbTextpage" -> 1053
Textpage = ContentBuilder.CreateSimpleContent(ContentType);
Textpage.Key = new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0");
ContentService.Save(Textpage, 0);
[SetUp]
public void Setup() => CreateTestData();
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1054
Subpage = ContentBuilder.CreateSimpleContent(ContentType, "Text Page 1", Textpage.Id);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
ContentService.Save(Subpage, 0, contentSchedule);
public virtual void CreateTestData()
{
// NOTE Maybe not the best way to create/save test data as we are using the services, which are being tested.
Template template = TemplateBuilder.CreateTextPageTemplate();
FileService.SaveTemplate(template);
// Create and Save ContentType "umbTextpage" -> 1051 (template), 1052 (content type)
ContentType = ContentTypeBuilder.CreateSimpleContentType("umbTextpage", "Textpage", defaultTemplateId: template.Id);
ContentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522");
ContentTypeService.Save(ContentType);
// Create and Save Content "Homepage" based on "umbTextpage" -> 1053
Textpage = ContentBuilder.CreateSimpleContent(ContentType);
Textpage.Key = new Guid("B58B3AD4-62C2-4E27-B1BE-837BD7C533E0");
ContentService.Save(Textpage, 0);
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1054
Subpage = ContentBuilder.CreateSimpleContent(ContentType, "Text Page 1", Textpage.Id);
var contentSchedule = ContentScheduleCollection.CreateWithEntry(DateTime.Now.AddMinutes(-5), null);
ContentService.Save(Subpage, 0, contentSchedule);
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1055
Subpage2 = ContentBuilder.CreateSimpleContent(ContentType, "Text Page 2", Textpage.Id);
ContentService.Save(Subpage2, 0);
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1055
Subpage2 = ContentBuilder.CreateSimpleContent(ContentType, "Text Page 2", Textpage.Id);
ContentService.Save(Subpage2, 0);
Subpage3 = ContentBuilder.CreateSimpleContent(ContentType, "Text Page 3", Textpage.Id);
ContentService.Save(Subpage3, 0);
Subpage3 = ContentBuilder.CreateSimpleContent(ContentType, "Text Page 3", Textpage.Id);
ContentService.Save(Subpage3, 0);
// Create and Save Content "Text Page Deleted" based on "umbTextpage" -> 1056
Trashed = ContentBuilder.CreateSimpleContent(ContentType, "Text Page Deleted", -20);
Trashed.Trashed = true;
ContentService.Save(Trashed, 0);
}
protected Content Trashed { get; private set; }
protected Content Subpage2 { get; private set; }
protected Content Subpage3 { get; private set; }
protected Content Subpage { get; private set; }
protected Content Textpage { get; private set; }
protected ContentType ContentType { get; private set; }
// Create and Save Content "Text Page Deleted" based on "umbTextpage" -> 1056
Trashed = ContentBuilder.CreateSimpleContent(ContentType, "Text Page Deleted", -20);
Trashed.Trashed = true;
ContentService.Save(Trashed, 0);
}
}