Merge branch 'netcore/dev' into netcore/feature/ab5820-webprofiler-aspnetcore

This commit is contained in:
Elitsa Marinovska
2020-03-30 15:26:51 +02:00
committed by GitHub
34 changed files with 1435 additions and 635 deletions

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Configuration.Models
/// </summary>
internal class GlobalSettings : IGlobalSettings
{
private const string Prefix = Constants.Configuration.ConfigPrefix + "Global:";
public const string Prefix = Constants.Configuration.ConfigPrefix + "Global:";
internal const string
StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,"; //must end with a comma!

View File

@@ -32,4 +32,10 @@
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Umbraco.Tests.Integration</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>

View File

@@ -13,7 +13,7 @@ namespace Umbraco.Core.Configuration
private readonly IGlobalSettings _globalSettings;
public UmbracoVersion(IGlobalSettings globalSettings)
:this()
: this()
{
_globalSettings = globalSettings;
}
@@ -55,7 +55,7 @@ namespace Umbraco.Core.Configuration
/// <para>Is the one that the CLR checks for compatibility. Therefore, it changes only on
/// hard-breaking changes (for instance, on new major versions).</para>
/// </remarks>
public Version AssemblyVersion {get; }
public Version AssemblyVersion { get; }
/// <summary>
/// Gets the assembly file version of the Umbraco code.
@@ -83,11 +83,13 @@ namespace Umbraco.Core.Configuration
/// and changes during an upgrade. The executing code version changes when new code is
/// deployed. The site/files version changes during an upgrade.</para>
/// </remarks>
public SemVersion LocalVersion {
public SemVersion LocalVersion
{
get
{
var value = _globalSettings.ConfigurationStatus;
return value.IsNullOrWhiteSpace() ? null : SemVersion.TryParse(value, out var semver) ? semver : null;
} }
}
}
}
}

View File

@@ -130,7 +130,7 @@ namespace Umbraco.Core.Migrations.Install
if (e.Cancel == false)
{
var dataCreation = new DatabaseDataCreator(_database, _logger,_umbracoVersion, _globalSettings);
var dataCreation = new DatabaseDataCreator(_database, _logger, _umbracoVersion, _globalSettings);
foreach (var table in OrderedTables)
CreateTable(false, table, dataCreation);
}
@@ -451,7 +451,7 @@ namespace Umbraco.Core.Migrations.Install
//Execute the Create Table sql
var created = _database.Execute(new Sql(createSql));
_logger.Info<DatabaseSchemaCreator>("Create Table {TableName} ({Created}): \n {Sql}", tableName, created, createSql);
_logger.Info<DatabaseSchemaCreator>("Create Table {TableName} ({Created}): \n {Sql}", tableName, created, createSql);
//If any statements exists for the primary key execute them here
if (string.IsNullOrEmpty(createPrimaryKeySql) == false)
@@ -487,11 +487,11 @@ namespace Umbraco.Core.Migrations.Install
if (overwrite)
{
_logger.Info<Database>("Table {TableName} was recreated", tableName);
_logger.Info<Database>("Table {TableName} was recreated", tableName);
}
else
{
_logger.Info<Database>("New table {TableName} was created", tableName);
_logger.Info<Database>("New table {TableName} was created", tableName);
}
}

View File

@@ -253,6 +253,11 @@ namespace Umbraco.Core.Persistence
_masterCstr = $@"Server=(localdb)\{instanceName};Integrated Security=True;";
}
public static string GetConnectionString(string instanceName, string databaseName)
{
return $@"Server=(localdb)\{instanceName};Integrated Security=True;Database={databaseName};";
}
/// <summary>
/// Gets a LocalDb connection string.
/// </summary>

View File

@@ -319,7 +319,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private void PersistAllowedSections(IUserGroup entity)
{
var userGroup = (UserGroup) entity;
var userGroup = entity;
// First delete all
Database.Delete<UserGroup2AppDto>("WHERE UserGroupId = @UserGroupId", new { UserGroupId = userGroup.Id });

View File

@@ -28,6 +28,7 @@ namespace Umbraco.Core.Persistence
private readonly RetryPolicy _connectionRetryPolicy;
private readonly RetryPolicy _commandRetryPolicy;
private readonly Guid _instanceGuid = Guid.NewGuid();
private List<CommandInfo> _commands;
#region Ctor
@@ -162,6 +163,14 @@ namespace Umbraco.Core.Persistence
/// </summary>
public int SqlCount { get; private set; }
internal bool LogCommands
{
get => _commands != null;
set => _commands = value ? new List<CommandInfo>() : null;
}
internal IEnumerable<CommandInfo> Commands => _commands;
public int BulkInsertRecords<T>(IEnumerable<T> records)
{
return _bulkSqlInsertProvider.BulkInsertRecords(this, records);
@@ -267,9 +276,43 @@ namespace Umbraco.Core.Persistence
if (_enableCount)
SqlCount++;
_commands?.Add(new CommandInfo(cmd));
base.OnExecutedCommand(cmd);
}
#endregion
// used for tracking commands
public class CommandInfo
{
public CommandInfo(IDbCommand cmd)
{
Text = cmd.CommandText;
var parameters = new List<ParameterInfo>();
foreach (IDbDataParameter parameter in cmd.Parameters) parameters.Add(new ParameterInfo(parameter));
Parameters = parameters.ToArray();
}
public string Text { get; }
public ParameterInfo[] Parameters { get; }
}
// used for tracking commands
public class ParameterInfo
{
public ParameterInfo(IDbDataParameter parameter)
{
Name = parameter.ParameterName;
Value = parameter.Value;
DbType = parameter.DbType;
Size = parameter.Size;
}
public string Name { get; }
public object Value { get; }
public DbType DbType { get; }
public int Size { get; }
}
}
}

View File

@@ -29,7 +29,6 @@ namespace Umbraco.Core.Persistence
{
private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator;
private readonly IGlobalSettings _globalSettings;
private readonly IConnectionStrings _connectionStrings;
private readonly Lazy<IMapperCollection> _mappers;
private readonly ILogger _logger;
@@ -37,7 +36,6 @@ namespace Umbraco.Core.Persistence
private DatabaseFactory _npocoDatabaseFactory;
private IPocoDataFactory _pocoDataFactory;
private string _connectionString;
private string _providerName;
private DatabaseType _databaseType;
private ISqlSyntaxProvider _sqlSyntax;
@@ -73,7 +71,7 @@ namespace Umbraco.Core.Persistence
/// </summary>
/// <remarks>Used by core runtime.</remarks>
public UmbracoDatabaseFactory(ILogger logger, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, Lazy<IMapperCollection> mappers,IDbProviderFactoryCreator dbProviderFactoryCreator)
: this(Constants.System.UmbracoConnectionName, globalSettings, connectionStrings, logger, mappers, dbProviderFactoryCreator)
: this(logger, globalSettings, connectionStrings, Constants.System.UmbracoConnectionName, mappers, dbProviderFactoryCreator)
{
}
@@ -82,13 +80,12 @@ namespace Umbraco.Core.Persistence
/// Initializes a new instance of the <see cref="UmbracoDatabaseFactory"/>.
/// </summary>
/// <remarks>Used by the other ctor and in tests.</remarks>
public UmbracoDatabaseFactory(string connectionStringName, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, ILogger logger, Lazy<IMapperCollection> mappers, IDbProviderFactoryCreator dbProviderFactoryCreator)
public UmbracoDatabaseFactory(ILogger logger, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, string connectionStringName, Lazy<IMapperCollection> mappers, IDbProviderFactoryCreator dbProviderFactoryCreator)
{
if (connectionStringName == null) throw new ArgumentNullException(nameof(connectionStringName));
if (string.IsNullOrWhiteSpace(connectionStringName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(connectionStringName));
_globalSettings = globalSettings;
_connectionStrings = connectionStrings;
_mappers = mappers ?? throw new ArgumentNullException(nameof(mappers));
_dbProviderFactoryCreator = dbProviderFactoryCreator ?? throw new ArgumentNullException(nameof(dbProviderFactoryCreator));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -118,7 +115,7 @@ namespace Umbraco.Core.Persistence
/// Initializes a new instance of the <see cref="UmbracoDatabaseFactory"/>.
/// </summary>
/// <remarks>Used in tests.</remarks>
public UmbracoDatabaseFactory(string connectionString, string providerName, ILogger logger, Lazy<IMapperCollection> mappers, IDbProviderFactoryCreator dbProviderFactoryCreator)
public UmbracoDatabaseFactory(ILogger logger, string connectionString, string providerName, Lazy<IMapperCollection> mappers, IDbProviderFactoryCreator dbProviderFactoryCreator)
{
_mappers = mappers ?? throw new ArgumentNullException(nameof(mappers));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -142,7 +139,7 @@ namespace Umbraco.Core.Persistence
{
lock (_lock)
{
return !_connectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace();
return !ConnectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace();
}
}
}
@@ -151,13 +148,13 @@ namespace Umbraco.Core.Persistence
public bool Initialized => Volatile.Read(ref _initialized);
/// <inheritdoc />
public string ConnectionString => _connectionString;
public string ConnectionString { get; private set; }
/// <inheritdoc />
public bool CanConnect =>
// actually tries to connect to the database (regardless of configured/initialized)
!_connectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace() &&
DbConnectionExtensions.IsConnectionAvailable(_connectionString, DbProviderFactory);
!ConnectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace() &&
DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DbProviderFactory);
private void UpdateSqlServerDatabaseType()
{
@@ -169,7 +166,7 @@ namespace Umbraco.Core.Persistence
if (setting.IsNullOrWhiteSpace() || !setting.StartsWith("SqlServer.")
|| !Enum<SqlServerSyntaxProvider.VersionName>.TryParse(setting.Substring("SqlServer.".Length), out var versionName, true))
{
versionName = ((SqlServerSyntaxProvider) _sqlSyntax).GetSetVersion(_connectionString, _providerName, _logger).ProductVersionName;
versionName = ((SqlServerSyntaxProvider) _sqlSyntax).GetSetVersion(ConnectionString, _providerName, _logger).ProductVersionName;
}
else
{
@@ -233,7 +230,7 @@ namespace Umbraco.Core.Persistence
if (Volatile.Read(ref _initialized))
throw new InvalidOperationException("Already initialized.");
_connectionString = connectionString;
ConnectionString = connectionString;
_providerName = providerName;
}
@@ -249,18 +246,19 @@ namespace Umbraco.Core.Persistence
{
_logger.Debug<UmbracoDatabaseFactory>("Initializing.");
if (_connectionString.IsNullOrWhiteSpace()) throw new InvalidOperationException("The factory has not been configured with a proper connection string.");
if (ConnectionString.IsNullOrWhiteSpace()) throw new InvalidOperationException("The factory has not been configured with a proper connection string.");
if (_providerName.IsNullOrWhiteSpace()) throw new InvalidOperationException("The factory has not been configured with a proper provider name.");
if (DbProviderFactory == null)
throw new Exception($"Can't find a provider factory for provider name \"{_providerName}\".");
// cannot initialize without being able to talk to the database
if (!DbConnectionExtensions.IsConnectionAvailable(_connectionString, DbProviderFactory))
// TODO: Why not?
if (!DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DbProviderFactory))
throw new Exception("Cannot connect to the database.");
_connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(_connectionString);
_commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(_connectionString);
_connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(ConnectionString);
_commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(ConnectionString);
_databaseType = DatabaseType.Resolve(DbProviderFactory.GetType().Name, _providerName);
@@ -313,7 +311,7 @@ namespace Umbraco.Core.Persistence
// method used by NPoco's UmbracoDatabaseFactory to actually create the database instance
private UmbracoDatabase CreateDatabaseInstance()
{
return new UmbracoDatabase(_connectionString, SqlContext, DbProviderFactory, _logger, _bulkSqlInsertProvider, _connectionRetryPolicy, _commandRetryPolicy);
return new UmbracoDatabase(ConnectionString, SqlContext, DbProviderFactory, _logger, _bulkSqlInsertProvider, _connectionRetryPolicy, _commandRetryPolicy);
}
protected override void DisposeResources()

View File

@@ -33,14 +33,12 @@ namespace Umbraco.Core.Runtime
// unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer
_lockId = Guid.NewGuid().ToString();
_logger = logger;
_dbFactory = new UmbracoDatabaseFactory(
Constants.System.UmbracoConnectionName,
_dbFactory = new UmbracoDatabaseFactory(_logger,
globalSettings,
connectionStrings,
_logger,
Constants.System.UmbracoConnectionName,
new Lazy<IMapperCollection>(() => new MapperCollection(Enumerable.Empty<BaseMapper>())),
dbProviderFactoryCreator
);
dbProviderFactoryCreator);
}
public async Task<bool> AcquireLockAsync(int millisecondsTimeout)

View File

@@ -0,0 +1,106 @@
using System;
using System.Linq;
using Moq;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models.Membership;
using Umbraco.Tests.Common.Builders.Interfaces;
namespace Umbraco.Tests.Common.Builders
{
public class UserBuilder
: BuilderBase<User>,
IWithIdBuilder
{
private int? _id;
private string _language;
private bool? _approved;
private string _name;
private string _rawPassword;
private bool? _isLockedOut;
private string _email;
private string _username;
private string _defaultLang;
private string _suffix = string.Empty;
public UserBuilder WithDefaultUILanguage(string defaultLang)
{
_defaultLang = defaultLang;
return this;
}
public UserBuilder WithLanguage(string language)
{
_language = language;
return this;
}
public UserBuilder WithApproved(bool approved)
{
_approved = approved;
return this;
}
public UserBuilder WithRawPassword(string rawPassword)
{
_rawPassword = rawPassword;
return this;
}
public UserBuilder WithEmail(string email)
{
_email = email;
return this;
}
public UserBuilder WithUsername(string username)
{
_username = username;
return this;
}
public UserBuilder WithLockedOut(bool isLockedOut)
{
_isLockedOut = isLockedOut;
return this;
}
public UserBuilder WithName(string name)
{
_name = name;
return this;
}
/// <summary>
/// Will suffix the name, email and username for testing
/// </summary>
/// <param name="suffix"></param>
/// <returns></returns>
public UserBuilder WithSuffix(string suffix)
{
_suffix = suffix;
return this;
}
public override User Build()
{
var globalSettings = Mock.Of<IGlobalSettings>(x => x.DefaultUILanguage == (_defaultLang ?? "en-US"));
return new User(globalSettings,
_name ?? "TestUser" + _suffix,
_email ?? "test" + _suffix + "@test.com",
_username ?? "TestUser" + _suffix,
_rawPassword ?? "abcdefghijklmnopqrstuvwxyz")
{
Language = _language ?? _defaultLang ?? "en-US",
IsLockedOut = _isLockedOut ?? false,
IsApproved = _approved ?? true
};
}
int? IWithIdBuilder.Id
{
get => _id;
set => _id = value;
}
}
}

View File

@@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.Linq;
using Moq;
using Umbraco.Core.Models.Membership;
using Umbraco.Tests.Common.Builders.Interfaces;
namespace Umbraco.Tests.Common.Builders
{
public class UserGroupBuilder
: BuilderBase<IUserGroup>,
IWithIdBuilder
{
private int? _startContentId;
private int? _startMediaId;
private string _alias;
private string _icon;
private string _name;
private IEnumerable<string> _permissions = Enumerable.Empty<string>();
private IEnumerable<string> _sectionCollection = Enumerable.Empty<string>();
private string _suffix;
private int? _id;
/// <summary>
/// Will suffix the name and alias for testing
/// </summary>
/// <param name="suffix"></param>
/// <returns></returns>
public UserGroupBuilder WithSuffix(string suffix)
{
_suffix = suffix;
return this;
}
public IReadOnlyUserGroup BuildReadOnly(IUserGroup userGroup)
{
return Mock.Of<IReadOnlyUserGroup>(x =>
x.Permissions == userGroup.Permissions &&
x.Alias == userGroup.Alias &&
x.Icon == userGroup.Icon &&
x.Name == userGroup.Name &&
x.StartContentId == userGroup.StartContentId &&
x.StartMediaId == userGroup.StartMediaId &&
x.AllowedSections == userGroup.AllowedSections &&
x.Id == userGroup.Id);
}
public override IUserGroup Build()
{
return Mock.Of<IUserGroup>(x =>
x.StartContentId == _startContentId &&
x.StartMediaId == _startMediaId &&
x.Name == (_name ?? ("TestUserGroup" + _suffix)) &&
x.Alias == (_alias ?? ("testUserGroup" + _suffix)) &&
x.Icon == _icon &&
x.Permissions == _permissions &&
x.AllowedSections == _sectionCollection);
}
int? IWithIdBuilder.Id
{
get => _id;
set => _id = value;
}
}
}

View File

@@ -31,10 +31,14 @@ namespace Umbraco.Tests.Testing
var test = TestContext.CurrentContext.Test;
var typeName = test.ClassName;
var methodName = test.MethodName;
// this will only get types from whatever is already loaded in the app domain
var type = Type.GetType(typeName, false);
if (type == null)
{
type = ScanAssemblies
// automatically add the executing and calling assemblies to the list to scan for this type
var scanAssemblies = ScanAssemblies.Union(new[] {Assembly.GetExecutingAssembly(), Assembly.GetCallingAssembly()}).ToList();
type = scanAssemblies
.Select(assembly => assembly.GetType(typeName, false))
.FirstOrDefault(x => x != null);
if (type == null)

View File

@@ -0,0 +1,61 @@
namespace Umbraco.Tests.Testing
{
public static class UmbracoTestOptions
{
public enum Logger
{
/// <summary>
/// pure mocks
/// </summary>
Mock,
/// <summary>
/// Serilog for tests
/// </summary>
Serilog,
/// <summary>
/// console logger
/// </summary>
Console
}
public enum Database
{
/// <summary>
/// no database
/// </summary>
None,
/// <summary>
/// new empty database file for the entire fixture
/// </summary>
NewEmptyPerFixture,
/// <summary>
/// new empty database file per test
/// </summary>
NewEmptyPerTest,
/// <summary>
/// new database file with schema for the entire fixture
/// </summary>
NewSchemaPerFixture,
/// <summary>
/// new database file with schema per test
/// </summary>
NewSchemaPerTest
}
public enum TypeLoader
{
/// <summary>
/// the default, global type loader for tests
/// </summary>
Default,
/// <summary>
/// create one type loader for the feature
/// </summary>
PerFixture,
/// <summary>
/// create one type loader for each test
/// </summary>
PerTest
}
}
}

View File

@@ -13,6 +13,7 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.Persistence;
using Umbraco.Tests.Common;
using Umbraco.Tests.Integration.Implementations;
using Umbraco.Tests.Integration.Testing;
namespace Umbraco.Tests.Integration
{
@@ -82,7 +83,7 @@ namespace Umbraco.Tests.Integration
// it means the container won't be disposed, and maybe other services? not sure.
// In cases where we use it can we use IConfigureOptions? https://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/
var umbracoContainer = RuntimeTests.GetUmbracoContainer(out var serviceProviderFactory);
var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory);
IHostApplicationLifetime lifetime1 = null;

View File

@@ -0,0 +1,117 @@
using System;
using System.Data.Common;
using System.Data.SqlClient;
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Configuration.Models;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Tests.Integration.Testing;
using Umbraco.Tests.Testing;
namespace Umbraco.Tests.Integration.Extensions
{
public static class ApplicationBuilderExtensions
{
/// <summary>
/// Creates a LocalDb instance to use for the test
/// </summary>
/// <param name="app"></param>
/// <param name="dbFilePath"></param>
/// <param name="integrationTest"></param>
/// <returns></returns>
public static IApplicationBuilder UseTestLocalDb(this IApplicationBuilder app,
string dbFilePath,
UmbracoIntegrationTest integrationTest)
{
// get the currently set db options
var testOptions = TestOptionAttributeBase.GetTestOptions<UmbracoTestAttribute>();
if (testOptions.Database == UmbracoTestOptions.Database.None)
return app;
// need to manually register this factory
DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance);
if (!Directory.Exists(dbFilePath))
Directory.CreateDirectory(dbFilePath);
var db = UmbracoIntegrationTest.GetOrCreate(dbFilePath,
app.ApplicationServices.GetRequiredService<ILogger>(),
app.ApplicationServices.GetRequiredService<IGlobalSettings>(),
app.ApplicationServices.GetRequiredService<IUmbracoDatabaseFactory>());
switch (testOptions.Database)
{
case UmbracoTestOptions.Database.NewSchemaPerTest:
// Add teardown callback
integrationTest.OnTestTearDown(() => db.Detach());
// New DB + Schema
db.AttachSchema();
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
var dbFactory = app.ApplicationServices.GetRequiredService<IUmbracoDatabaseFactory>();
if (!dbFactory.Configured)
{
dbFactory.Configure(db.ConnectionString, Umbraco.Core.Constants.DatabaseProviders.SqlServer);
}
// In the case that we've initialized the schema, it means that we are installed so we'll want to ensure that
// the runtime state is configured correctly so we'll force update the configuration flag and re-run the
// runtime state checker.
// TODO: This wouldn't be required if we don't store the Umbraco version in config
// right now we are an an 'Install' state
var runtimeState = (RuntimeState)app.ApplicationServices.GetRequiredService<IRuntimeState>();
Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level);
// dynamically change the config status
var umbVersion = app.ApplicationServices.GetRequiredService<IUmbracoVersion>();
var config = app.ApplicationServices.GetRequiredService<IConfiguration>();
config[GlobalSettings.Prefix + "ConfigurationStatus"] = umbVersion.SemanticVersion.ToString();
// re-run the runtime level check
var profilingLogger = app.ApplicationServices.GetRequiredService<IProfilingLogger>();
runtimeState.DetermineRuntimeLevel(dbFactory, profilingLogger);
Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level);
break;
case UmbracoTestOptions.Database.NewEmptyPerTest:
// Add teardown callback
integrationTest.OnTestTearDown(() => db.Detach());
db.AttachEmpty();
break;
case UmbracoTestOptions.Database.NewSchemaPerFixture:
// Add teardown callback
integrationTest.OnFixtureTearDown(() => db.Detach());
break;
case UmbracoTestOptions.Database.NewEmptyPerFixture:
// Add teardown callback
integrationTest.OnFixtureTearDown(() => db.Detach());
break;
default:
throw new ArgumentOutOfRangeException(nameof(testOptions), testOptions, null);
}
return app;
}
}
}

View File

@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Tests.Integration.Implementations;
namespace Umbraco.Tests.Integration.Extensions
{
public static class ServiceCollectionExtensions
{
/// <summary>
/// These services need to be manually added because they do not get added by the generic host
/// </summary>
/// <param name="services"></param>
/// <param name="testHelper"></param>
/// <param name="webHostEnvironment"></param>
public static void AddRequiredNetCoreServices(this IServiceCollection services, TestHelper testHelper, IWebHostEnvironment webHostEnvironment)
{
services.AddSingleton<IHttpContextAccessor>(x => testHelper.GetHttpContextAccessor());
// the generic host does add IHostEnvironment but not this one because we are not actually in a web context
services.AddSingleton<IWebHostEnvironment>(x => webHostEnvironment);
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using NUnit.Framework;
using Umbraco.Tests.Integration.Testing;
// this class has NO NAMESPACE
// it applies to the whole assembly
[SetUpFixture]
// ReSharper disable once CheckNamespace
public class TestsSetup
{
private Stopwatch _stopwatch;
[OneTimeSetUp]
public void SetUp()
{
_stopwatch = Stopwatch.StartNew();
}
[OneTimeTearDown]
public void TearDown()
{
LocalDbTestDatabase.KillLocalDb();
Console.WriteLine("TOTAL TESTS DURATION: {0}", _stopwatch.Elapsed);
}
}

View File

@@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlClient;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Umbraco.Core;
using Umbraco.Tests.Integration.Testing;
namespace Umbraco.Tests.Integration.Implementations
{
public static class HostBuilderExtensions
{
public static IHostBuilder UseLocalDb(this IHostBuilder hostBuilder, string dbFilePath)
{
// Need to register SqlClient manually
// TODO: Move this to someplace central
DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance);
hostBuilder.ConfigureAppConfiguration(x =>
{
if (!Directory.Exists(dbFilePath))
Directory.CreateDirectory(dbFilePath);
var dbName = Guid.NewGuid().ToString("N");
var instance = TestLocalDb.EnsureLocalDbInstanceAndDatabase(dbName, dbFilePath);
x.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>($"ConnectionStrings:{Constants.System.UmbracoConnectionName}", instance.GetConnectionString(dbName))
});
});
return hostBuilder;
}
}
}

View File

@@ -1,29 +1,29 @@
using System.Linq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Integration.Testing;
using Umbraco.Tests.Testing;
using Umbraco.Core;
namespace Umbraco.Tests.Persistence.Repositories
namespace Umbraco.Tests.Integration.Persistence.Repositories
{
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)]
public class AuditRepositoryTest : TestWithDatabaseBase
public class AuditRepositoryTest : UmbracoIntegrationTest
{
[Test]
public void Can_Add_Audit_Entry()
{
var sp = TestObjects.GetScopeProvider(Logger);
using (var scope = sp.CreateScope())
var sp = ScopeProvider;
using (var scope = ScopeProvider.CreateScope())
{
var repo = new AuditRepository((IScopeAccessor) sp, Logger);
repo.Save(new AuditItem(-1, AuditType.System, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), "This is a System audit trail"));
var repo = new AuditRepository((IScopeAccessor)sp, Logger);
repo.Save(new AuditItem(-1, AuditType.System, -1, UmbracoObjectTypes.Document.GetName(), "This is a System audit trail"));
var dtos = scope.Database.Fetch<LogDto>("WHERE id > -1");
@@ -35,15 +35,15 @@ namespace Umbraco.Tests.Persistence.Repositories
[Test]
public void Get_Paged_Items()
{
var sp = TestObjects.GetScopeProvider(Logger);
var sp = ScopeProvider;
using (var scope = sp.CreateScope())
{
var repo = new AuditRepository((IScopeAccessor) sp, Logger);
var repo = new AuditRepository((IScopeAccessor)sp, Logger);
for (var i = 0; i < 100; i++)
{
repo.Save(new AuditItem(i, AuditType.New, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} created"));
repo.Save(new AuditItem(i, AuditType.Publish, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} published"));
repo.Save(new AuditItem(i, AuditType.New, -1, UmbracoObjectTypes.Document.GetName(), $"Content {i} created"));
repo.Save(new AuditItem(i, AuditType.Publish, -1, UmbracoObjectTypes.Document.GetName(), $"Content {i} published"));
}
scope.Complete();
@@ -51,7 +51,7 @@ namespace Umbraco.Tests.Persistence.Repositories
using (var scope = sp.CreateScope())
{
var repo = new AuditRepository((IScopeAccessor) sp, Logger);
var repo = new AuditRepository((IScopeAccessor)sp, Logger);
var page = repo.GetPagedResultsByQuery(sp.SqlContext.Query<IAuditItem>(), 0, 10, out var total, Direction.Descending, null, null);
@@ -63,15 +63,15 @@ namespace Umbraco.Tests.Persistence.Repositories
[Test]
public void Get_Paged_Items_By_User_Id_With_Query_And_Filter()
{
var sp = TestObjects.GetScopeProvider(Logger);
var sp = ScopeProvider;
using (var scope = sp.CreateScope())
{
var repo = new AuditRepository((IScopeAccessor)sp, Logger);
for (var i = 0; i < 100; i++)
{
repo.Save(new AuditItem(i, AuditType.New, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} created"));
repo.Save(new AuditItem(i, AuditType.Publish, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} published"));
repo.Save(new AuditItem(i, AuditType.New, -1, UmbracoObjectTypes.Document.GetName(), $"Content {i} created"));
repo.Save(new AuditItem(i, AuditType.Publish, -1, UmbracoObjectTypes.Document.GetName(), $"Content {i} published"));
}
scope.Complete();
@@ -106,15 +106,15 @@ namespace Umbraco.Tests.Persistence.Repositories
[Test]
public void Get_Paged_Items_With_AuditType_Filter()
{
var sp = TestObjects.GetScopeProvider(Logger);
var sp = ScopeProvider;
using (var scope = sp.CreateScope())
{
var repo = new AuditRepository((IScopeAccessor) sp, Logger);
var repo = new AuditRepository((IScopeAccessor)sp, Logger);
for (var i = 0; i < 100; i++)
{
repo.Save(new AuditItem(i, AuditType.New, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} created"));
repo.Save(new AuditItem(i, AuditType.Publish, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), $"Content {i} published"));
repo.Save(new AuditItem(i, AuditType.New, -1, UmbracoObjectTypes.Document.GetName(), $"Content {i} created"));
repo.Save(new AuditItem(i, AuditType.Publish, -1, UmbracoObjectTypes.Document.GetName(), $"Content {i} published"));
}
scope.Complete();
@@ -122,10 +122,10 @@ namespace Umbraco.Tests.Persistence.Repositories
using (var scope = sp.CreateScope())
{
var repo = new AuditRepository((IScopeAccessor) sp, Logger);
var repo = new AuditRepository((IScopeAccessor)sp, Logger);
var page = repo.GetPagedResultsByQuery(sp.SqlContext.Query<IAuditItem>(), 0, 9, out var total, Direction.Descending,
new[] {AuditType.Publish}, null)
new[] { AuditType.Publish }, null)
.ToArray();
Assert.AreEqual(9, page.Length);
@@ -137,15 +137,15 @@ namespace Umbraco.Tests.Persistence.Repositories
[Test]
public void Get_Paged_Items_With_Custom_Filter()
{
var sp = TestObjects.GetScopeProvider(Logger);
var sp = ScopeProvider;
using (var scope = sp.CreateScope())
{
var repo = new AuditRepository((IScopeAccessor) sp, Logger);
var repo = new AuditRepository((IScopeAccessor)sp, Logger);
for (var i = 0; i < 100; i++)
{
repo.Save(new AuditItem(i, AuditType.New, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), "Content created"));
repo.Save(new AuditItem(i, AuditType.Publish, -1, ObjectTypes.GetName(UmbracoObjectTypes.Document), "Content published"));
repo.Save(new AuditItem(i, AuditType.New, -1, UmbracoObjectTypes.Document.GetName(), "Content created"));
repo.Save(new AuditItem(i, AuditType.Publish, -1, UmbracoObjectTypes.Document.GetName(), "Content published"));
}
scope.Complete();
@@ -153,7 +153,7 @@ namespace Umbraco.Tests.Persistence.Repositories
using (var scope = sp.CreateScope())
{
var repo = new AuditRepository((IScopeAccessor) sp, Logger);
var repo = new AuditRepository((IScopeAccessor)sp, Logger);
var page = repo.GetPagedResultsByQuery(sp.SqlContext.Query<IAuditItem>(), 0, 8, out var total, Direction.Descending,
null, sp.SqlContext.Query<IAuditItem>().Where(item => item.Comment == "Content created"))

View File

@@ -0,0 +1,393 @@
using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Scoping;
using Umbraco.Tests.Testing;
using Umbraco.Core.Persistence;
using Umbraco.Core.PropertyEditors;
using System;
using Umbraco.Core.Configuration;
using Umbraco.Core.Services.Implement;
using Umbraco.Tests.Integration.Testing;
namespace Umbraco.Tests.Persistence.Repositories
{
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true, Logger = UmbracoTestOptions.Logger.Console)]
public class UserRepositoryTest : UmbracoIntegrationTest
{
private UserRepository CreateRepository(IScopeProvider provider)
{
var accessor = (IScopeAccessor) provider;
var repository = new UserRepository(accessor, AppCaches.Disabled, Logger, Mappers, GlobalSettings, Mock.Of<IUserPasswordConfiguration>());
return repository;
}
private UserGroupRepository CreateUserGroupRepository(IScopeProvider provider)
{
var accessor = (IScopeAccessor) provider;
return new UserGroupRepository(accessor, AppCaches.Disabled, Logger, ShortStringHelper);
}
[Test]
public void Can_Perform_Add_On_UserRepository()
{
// Arrange
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var user = UserBuilder.Build();
// Act
repository.Save(user);
// Assert
Assert.That(user.HasIdentity, Is.True);
}
}
[Test]
public void Can_Perform_Multiple_Adds_On_UserRepository()
{
// Arrange
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var user1 = UserBuilder.WithSuffix("1").Build();
var use2 = UserBuilder.WithSuffix("2").Build();
// Act
repository.Save(user1);
repository.Save(use2);
// Assert
Assert.That(user1.HasIdentity, Is.True);
Assert.That(use2.HasIdentity, Is.True);
}
}
[Test]
public void Can_Verify_Fresh_Entity_Is_Not_Dirty()
{
// Arrange
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var user = UserBuilder.Build();
repository.Save(user);
// Act
var resolved = repository.Get((int)user.Id);
bool dirty = ((User)resolved).IsDirty();
// Assert
Assert.That(dirty, Is.False);
}
}
[Test]
public void Can_Perform_Delete_On_UserRepository()
{
// Arrange
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var user = UserBuilder.Build();
// Act
repository.Save(user);
var id = user.Id;
var repository2 = new UserRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, Mock.Of<IMapperCollection>(),GlobalSettings, Mock.Of<IUserPasswordConfiguration>());
repository2.Delete(user);
var resolved = repository2.Get((int) id);
// Assert
Assert.That(resolved, Is.Null);
}
}
[Test]
public void Can_Perform_Get_On_UserRepository()
{
// Arrange
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var userGroupRepository = CreateUserGroupRepository(provider);
var user = CreateAndCommitUserWithGroup(repository, userGroupRepository);
// Act
var updatedItem = repository.Get(user.Id);
// FIXME: this test cannot work, user has 2 sections but the way it's created,
// they don't show, so the comparison with updatedItem fails - fix!
// Assert
AssertPropertyValues(updatedItem, user);
}
}
[Test]
public void Can_Perform_GetByQuery_On_UserRepository()
{
// Arrange
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
CreateAndCommitMultipleUsers(repository);
// Act
var query = scope.SqlContext.Query<IUser>().Where(x => x.Username == "TestUser1");
var result = repository.Get(query);
// Assert
Assert.That(result.Count(), Is.GreaterThanOrEqualTo(1));
}
}
[Test]
public void Can_Perform_GetAll_By_Param_Ids_On_UserRepository()
{
// Arrange
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var users = CreateAndCommitMultipleUsers(repository);
// Act
var result = repository.GetMany((int) users[0].Id, (int) users[1].Id);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result.Any(), Is.True);
Assert.That(result.Count(), Is.EqualTo(2));
}
}
[Test]
public void Can_Perform_GetAll_On_UserRepository()
{
// Arrange
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
CreateAndCommitMultipleUsers(repository);
// Act
var result = repository.GetMany();
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result.Any(), Is.True);
Assert.That(result.Count(), Is.GreaterThanOrEqualTo(3));
}
}
[Test]
public void Can_Perform_Exists_On_UserRepository()
{
// Arrange
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var users = CreateAndCommitMultipleUsers(repository);
// Act
var exists = repository.Exists(users[0].Id);
// Assert
Assert.That(exists, Is.True);
}
}
[Test]
public void Can_Perform_Count_On_UserRepository()
{
// Arrange
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var users = CreateAndCommitMultipleUsers(repository);
// Act
var query = scope.SqlContext.Query<IUser>().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2");
var result = repository.Count(query);
// Assert
Assert.AreEqual(2, result);
}
}
[Test]
public void Can_Get_Paged_Results_By_Query_And_Filter_And_Groups()
{
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var users = CreateAndCommitMultipleUsers(repository);
var query = provider.SqlContext.Query<IUser>().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2");
try
{
scope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
scope.Database.AsUmbracoDatabase().EnableSqlCount = true;
// Act
var result = repository.GetPagedResultsByQuery(query, 0, 10, out var totalRecs, user => user.Id, Direction.Ascending,
excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias },
filter: provider.SqlContext.Query<IUser>().Where(x => x.Id > -1));
// Assert
Assert.AreEqual(2, totalRecs);
}
finally
{
scope.Database.AsUmbracoDatabase().EnableSqlTrace = false;
scope.Database.AsUmbracoDatabase().EnableSqlCount = false;
}
}
}
[Test]
public void Can_Get_Paged_Results_With_Filter_And_Groups()
{
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var users = CreateAndCommitMultipleUsers(repository);
try
{
scope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
scope.Database.AsUmbracoDatabase().EnableSqlCount = true;
// Act
var result = repository.GetPagedResultsByQuery(null, 0, 10, out var totalRecs, user => user.Id, Direction.Ascending,
includeUserGroups: new[] { Constants.Security.AdminGroupAlias, Constants.Security.SensitiveDataGroupAlias },
excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias },
filter: provider.SqlContext.Query<IUser>().Where(x => x.Id == -1));
// Assert
Assert.AreEqual(1, totalRecs);
}
finally
{
scope.Database.AsUmbracoDatabase().EnableSqlTrace = false;
scope.Database.AsUmbracoDatabase().EnableSqlCount = false;
}
}
}
[Test]
public void Can_Invalidate_SecurityStamp_On_Username_Change()
{
// Arrange
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var userGroupRepository = CreateUserGroupRepository(provider);
var user = CreateAndCommitUserWithGroup(repository, userGroupRepository);
var originalSecurityStamp = user.SecurityStamp;
// Ensure when user generated a security stamp is present
Assert.That(user.SecurityStamp, Is.Not.Null);
Assert.That(user.SecurityStamp, Is.Not.Empty);
// Update username
user.Username = user.Username + "UPDATED";
repository.Save(user);
// Get the user
var updatedUser = repository.Get(user.Id);
// Ensure the Security Stamp is invalidated & no longer the same
Assert.AreNotEqual(originalSecurityStamp, updatedUser.SecurityStamp);
}
}
private void AssertPropertyValues(IUser updatedItem, IUser originalUser)
{
Assert.That(updatedItem.Id, Is.EqualTo(originalUser.Id));
Assert.That(updatedItem.Name, Is.EqualTo(originalUser.Name));
Assert.That(updatedItem.Language, Is.EqualTo(originalUser.Language));
Assert.That(updatedItem.IsApproved, Is.EqualTo(originalUser.IsApproved));
Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(originalUser.RawPasswordValue));
Assert.That(updatedItem.IsLockedOut, Is.EqualTo(originalUser.IsLockedOut));
Assert.IsTrue(updatedItem.StartContentIds.UnsortedSequenceEqual(originalUser.StartContentIds));
Assert.IsTrue(updatedItem.StartMediaIds.UnsortedSequenceEqual(originalUser.StartMediaIds));
Assert.That(updatedItem.Email, Is.EqualTo(originalUser.Email));
Assert.That(updatedItem.Username, Is.EqualTo(originalUser.Username));
Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(originalUser.AllowedSections.Count()));
foreach (var allowedSection in originalUser.AllowedSections)
Assert.IsTrue(updatedItem.AllowedSections.Contains(allowedSection));
}
private User CreateAndCommitUserWithGroup(IUserRepository repository, IUserGroupRepository userGroupRepository)
{
var user = UserBuilder.Build();
repository.Save(user);
var group = UserGroupBuilder.Build();
userGroupRepository.AddOrUpdateGroupWithUsers(@group, new[] { user.Id });
user.AddGroup(UserGroupBuilder.BuildReadOnly(group));
return user;
}
private IUser[] CreateAndCommitMultipleUsers(IUserRepository repository)
{
var user1 = UserBuilder.WithSuffix("1").Build();
var user2 = UserBuilder.WithSuffix("2").Build();
var user3 = UserBuilder.WithSuffix("3").Build();
repository.Save(user1);
repository.Save(user2);
repository.Save(user3);
return new IUser[] { user1, user2, user3 };
}
}
}

View File

@@ -1,29 +1,22 @@
using LightInject;
using LightInject.Microsoft.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Moq;
using NUnit.Framework;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Composing.LightInject;
using Umbraco.Core.Logging;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence;
using Umbraco.Core.Runtime;
using Umbraco.Tests.Common;
using Umbraco.Tests.Integration.Extensions;
using Umbraco.Tests.Integration.Implementations;
using Umbraco.Tests.Integration.Testing;
using Umbraco.Web.BackOffice.AspNetCore;
using Umbraco.Web.Common.AspNetCore;
using static Umbraco.Core.Migrations.Install.DatabaseBuilder;
namespace Umbraco.Tests.Integration
{
@@ -38,10 +31,11 @@ namespace Umbraco.Tests.Integration
MyComposer.Reset();
}
[OneTimeTearDown]
public void FixtureTearDown()
[SetUp]
public void Setup()
{
TestLocalDb.Cleanup();
MyComponent.Reset();
MyComposer.Reset();
}
/// <summary>
@@ -94,7 +88,7 @@ namespace Umbraco.Tests.Integration
[Test]
public async Task AddUmbracoCore()
{
var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory);
var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory);
var testHelper = new TestHelper();
var hostBuilder = new HostBuilder()
@@ -102,7 +96,7 @@ namespace Umbraco.Tests.Integration
.ConfigureServices((hostContext, services) =>
{
var webHostEnvironment = testHelper.GetWebHostEnvironment();
AddRequiredNetCoreServices(services, testHelper, webHostEnvironment);
services.AddRequiredNetCoreServices(testHelper, webHostEnvironment);
// Add it!
services.AddUmbracoConfiguration(hostContext.Configuration);
@@ -133,7 +127,7 @@ namespace Umbraco.Tests.Integration
[Test]
public async Task UseUmbracoCore()
{
var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory);
var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory);
var testHelper = new TestHelper();
var hostBuilder = new HostBuilder()
@@ -141,7 +135,7 @@ namespace Umbraco.Tests.Integration
.ConfigureServices((hostContext, services) =>
{
var webHostEnvironment = testHelper.GetWebHostEnvironment();
AddRequiredNetCoreServices(services, testHelper, webHostEnvironment);
services.AddRequiredNetCoreServices(testHelper, webHostEnvironment);
// Add it!
services.AddUmbracoConfiguration(hostContext.Configuration);
@@ -168,71 +162,6 @@ namespace Umbraco.Tests.Integration
Assert.IsTrue(MyComponent.IsTerminated);
}
[Test]
public async Task Install_Database()
{
var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory);
var testHelper = new TestHelper();
var hostBuilder = new HostBuilder()
//TODO: Need to have a configured umb version for the runtime state
.UseLocalDb(Path.Combine(testHelper.CurrentAssemblyDirectory, "LocalDb"))
.UseUmbraco(serviceProviderFactory)
.ConfigureServices((hostContext, services) =>
{
var webHostEnvironment = testHelper.GetWebHostEnvironment();
AddRequiredNetCoreServices(services, testHelper, webHostEnvironment);
// Add it!
services.AddUmbracoConfiguration(hostContext.Configuration);
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly);
});
var host = await hostBuilder.StartAsync();
var app = new ApplicationBuilder(host.Services);
app.UseUmbracoCore();
var runtimeState = (RuntimeState)app.ApplicationServices.GetRequiredService<IRuntimeState>();
Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level);
var dbBuilder = app.ApplicationServices.GetRequiredService<DatabaseBuilder>();
Assert.IsNotNull(dbBuilder);
var canConnect = dbBuilder.CanConnectToDatabase;
Assert.IsTrue(canConnect);
var dbResult = dbBuilder.CreateSchemaAndData();
Assert.IsTrue(dbResult.Success);
// TODO: Get this to work ... but to do that we need to mock or pass in a current umbraco version
//var dbFactory = app.ApplicationServices.GetRequiredService<IUmbracoDatabaseFactory>();
//var profilingLogger = app.ApplicationServices.GetRequiredService<IProfilingLogger>();
//runtimeState.DetermineRuntimeLevel(dbFactory, profilingLogger);
//Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level);
}
internal static LightInjectContainer GetUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory)
{
var container = UmbracoServiceProviderFactory.CreateServiceContainer();
serviceProviderFactory = new UmbracoServiceProviderFactory(container);
var umbracoContainer = serviceProviderFactory.GetContainer();
return umbracoContainer;
}
/// <summary>
/// These services need to be manually added because they do not get added by the generic host
/// </summary>
/// <param name="services"></param>
/// <param name="testHelper"></param>
/// <param name="webHostEnvironment"></param>
private void AddRequiredNetCoreServices(IServiceCollection services, TestHelper testHelper, IWebHostEnvironment webHostEnvironment)
{
services.AddSingleton<IHttpContextAccessor>(x => testHelper.GetHttpContextAccessor());
// the generic host does add IHostEnvironment but not this one because we are not actually in a web context
services.AddSingleton<IWebHostEnvironment>(x => webHostEnvironment);
}
[RuntimeLevel(MinLevel = RuntimeLevel.Install)]
public class MyComposer : IUserComposer

View File

@@ -0,0 +1,326 @@
using System;
using System.Collections.Concurrent;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Threading;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence;
namespace Umbraco.Tests.Integration.Testing
{
/// <summary>
/// Manages a pool of LocalDb databases for integration testing
/// </summary>
public class LocalDbTestDatabase
{
public const string InstanceName = "UmbracoTests";
public const string DatabaseName = "UmbracoTests";
private readonly ILogger _logger;
private readonly IGlobalSettings _globalSettings;
private readonly LocalDb _localDb;
private readonly IUmbracoVersion _umbracoVersion;
private static LocalDb.Instance _instance;
private static string _filesPath;
private readonly IUmbracoDatabaseFactory _dbFactory;
private UmbracoDatabase.CommandInfo[] _dbCommands;
private string _currentCstr;
private static DatabasePool _emptyPool;
private static DatabasePool _schemaPool;
private DatabasePool _currentPool;
//It's internal because `Umbraco.Core.Persistence.LocalDb` is internal
internal LocalDbTestDatabase(ILogger logger, IGlobalSettings globalSettings, LocalDb localDb, string filesPath, IUmbracoDatabaseFactory dbFactory)
{
_umbracoVersion = new UmbracoVersion();
_logger = logger;
_globalSettings = globalSettings;
_localDb = localDb;
_filesPath = filesPath;
_dbFactory = dbFactory;
_instance = _localDb.GetInstance(InstanceName);
if (_instance != null) return;
if (_localDb.CreateInstance(InstanceName) == false)
throw new Exception("Failed to create a LocalDb instance.");
_instance = _localDb.GetInstance(InstanceName);
}
public string ConnectionString => _currentCstr ?? _instance.GetAttachedConnectionString("XXXXXX", _filesPath);
private void Create()
{
var tempName = Guid.NewGuid().ToString("N");
_instance.CreateDatabase(tempName, _filesPath);
_instance.DetachDatabase(tempName);
// there's probably a sweet spot to be found for size / parallel...
var s = ConfigurationManager.AppSettings["Umbraco.Tests.LocalDbTestDatabase.EmptyPoolSize"];
var emptySize = s == null ? 2 : int.Parse(s);
s = ConfigurationManager.AppSettings["Umbraco.Tests.LocalDbTestDatabase.EmptyPoolThreadCount"];
var emptyParallel = s == null ? 1 : int.Parse(s);
s = ConfigurationManager.AppSettings["Umbraco.Tests.LocalDbTestDatabase.SchemaPoolSize"];
var schemaSize = s == null ? 2 : int.Parse(s);
s = ConfigurationManager.AppSettings["Umbraco.Tests.LocalDbTestDatabase.SchemaPoolThreadCount"];
var schemaParallel = s == null ? 1 : int.Parse(s);
_emptyPool = new DatabasePool(_localDb, _instance, DatabaseName + "-Empty", tempName, _filesPath, emptySize, emptyParallel);
_schemaPool = new DatabasePool(_localDb, _instance, DatabaseName + "-Schema", tempName, _filesPath, schemaSize, schemaParallel, delete: true, prepare: RebuildSchema);
}
public void AttachEmpty()
{
if (_emptyPool == null)
Create();
_currentCstr = _emptyPool.AttachDatabase();
_currentPool = _emptyPool;
}
public void AttachSchema()
{
if (_schemaPool == null)
Create();
_currentCstr = _schemaPool.AttachDatabase();
_currentPool = _schemaPool;
}
public void Detach()
{
_currentPool.DetachDatabase();
}
private void RebuildSchema(DbConnection conn, IDbCommand cmd)
{
if (_dbCommands != null)
{
foreach (var dbCommand in _dbCommands)
{
if (dbCommand.Text.StartsWith("SELECT ")) continue;
cmd.CommandText = dbCommand.Text;
cmd.Parameters.Clear();
foreach (var parameterInfo in dbCommand.Parameters)
AddParameter(cmd, parameterInfo);
cmd.ExecuteNonQuery();
}
}
else
{
_dbFactory.Configure(conn.ConnectionString, Umbraco.Core.Constants.DatabaseProviders.SqlServer);
using var database = (UmbracoDatabase)_dbFactory.CreateDatabase();
// track each db command ran as part of creating the database so we can replay these
database.LogCommands = true;
using var trans = database.GetTransaction();
var creator = new DatabaseSchemaCreator(database, _logger, _umbracoVersion, _globalSettings);
creator.InitializeDatabaseSchema();
trans.Complete(); // commit it
_dbCommands = database.Commands.ToArray();
}
}
private 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);
}
public void Clear()
{
var filename = Path.Combine(_filesPath, DatabaseName).ToUpper();
foreach (var database in _instance.GetDatabases())
{
if (database.StartsWith(filename))
_instance.DropDatabase(database);
}
foreach (var file in Directory.EnumerateFiles(_filesPath))
{
if (file.EndsWith(".mdf") == false && file.EndsWith(".ldf") == false) continue;
File.Delete(file);
}
}
private static void ResetLocalDb(IDbCommand cmd)
{
// https://stackoverflow.com/questions/536350
cmd.CommandType = CommandType.Text;
cmd.CommandText = @"
declare @n char(1);
set @n = char(10);
declare @stmt nvarchar(max);
-- check constraints
select @stmt = isnull( @stmt + @n, '' ) +
'alter table [' + schema_name(schema_id) + '].[' + object_name( parent_object_id ) + '] drop constraint [' + name + ']'
from sys.check_constraints;
-- foreign keys
select @stmt = isnull( @stmt + @n, '' ) +
'alter table [' + schema_name(schema_id) + '].[' + object_name( parent_object_id ) + '] drop constraint [' + name + ']'
from sys.foreign_keys;
-- tables
select @stmt = isnull( @stmt + @n, '' ) +
'drop table [' + schema_name(schema_id) + '].[' + name + ']'
from sys.tables;
exec sp_executesql @stmt;
";
cmd.ExecuteNonQuery();
}
public static void KillLocalDb()
{
_emptyPool?.Stop();
_schemaPool?.Stop();
if (_filesPath == null)
return;
var filename = Path.Combine(_filesPath, DatabaseName).ToUpper();
foreach (var database in _instance.GetDatabases())
{
if (database.StartsWith(filename))
_instance.DropDatabase(database);
}
foreach (var file in Directory.EnumerateFiles(_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
}
}
}
private class DatabasePool
{
private readonly LocalDb _localDb;
private readonly LocalDb.Instance _instance;
private readonly string _filesPath;
private readonly string _name;
private readonly int _size;
private readonly string[] _cstrs;
private readonly BlockingCollection<int> _prepareQueue, _readyQueue;
private readonly Action<DbConnection, IDbCommand> _prepare;
private int _current;
public DatabasePool(LocalDb localDb, LocalDb.Instance instance, string name, string tempName, string filesPath, int size, int parallel = 1, Action<DbConnection, IDbCommand> prepare = null, bool delete = false)
{
_localDb = localDb;
_instance = instance;
_filesPath = filesPath;
_name = name;
_size = size;
_prepare = prepare;
_prepareQueue = new BlockingCollection<int>();
_readyQueue = new BlockingCollection<int>();
_cstrs = new string[_size];
for (var i = 0; i < size; i++)
localDb.CopyDatabaseFiles(tempName, filesPath, targetDatabaseName: name + "-" + i, overwrite: true, delete: delete && i == size - 1);
if (prepare == null)
{
for (var i = 0; i < size; i++)
_readyQueue.Add(i);
}
else
{
for (var i = 0; i < size; i++)
_prepareQueue.Add(i);
}
for (var i = 0; i < parallel; i++)
{
var thread = new Thread(PrepareThread);
thread.Start();
}
}
public string AttachDatabase()
{
try
{
_current = _readyQueue.Take();
}
catch (InvalidOperationException)
{
_current = 0;
return null;
}
return ConnectionString(_current);
}
public void DetachDatabase()
{
_prepareQueue.Add(_current);
}
private string ConnectionString(int i)
{
return _cstrs[i] ?? (_cstrs[i] = _instance.GetAttachedConnectionString(_name + "-" + i, _filesPath));
}
private void PrepareThread()
{
while (_prepareQueue.IsCompleted == false)
{
int i;
try
{
i = _prepareQueue.Take();
}
catch (InvalidOperationException)
{
continue;
}
using (var conn = new SqlConnection(ConnectionString(i)))
using (var cmd = conn.CreateCommand())
{
conn.Open();
ResetLocalDb(cmd);
_prepare?.Invoke(conn, cmd);
}
_readyQueue.Add(i);
}
}
public void Stop()
{
int i;
_prepareQueue.CompleteAdding();
while (_prepareQueue.TryTake(out i)) { }
_readyQueue.CompleteAdding();
while (_readyQueue.TryTake(out i)) { }
}
}
}
}

View File

@@ -1,49 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using Umbraco.Core.Persistence;
namespace Umbraco.Tests.Integration.Testing
{
public static class TestLocalDb
{
private const string LocalDbInstanceName = "UmbTests";
private static LocalDb LocalDb { get; } = new LocalDb();
// TODO: We need to borrow logic from this old branch, this is the latest commit at the old branch where we had LocalDb
// working for tests. There's a lot of hoops to jump through to make it work 'fast'. Turns out it didn't actually run as
// fast as SqlCe due to the dropping/creating of DB instances since that is faster in SqlCe but this code was all heavily
// optimized to go as fast as possible.
// see https://github.com/umbraco/Umbraco-CMS/blob/3a8716ac7b1c48b51258724337086cd0712625a1/src/Umbraco.Tests/TestHelpers/LocalDbTestDatabase.cs
internal static LocalDb.Instance EnsureLocalDbInstanceAndDatabase(string dbName, string dbFilePath)
{
if (!LocalDb.InstanceExists(LocalDbInstanceName) && !LocalDb.CreateInstance(LocalDbInstanceName))
{
throw new InvalidOperationException(
$"Failed to create LocalDb instance {LocalDbInstanceName}, assuming LocalDb is not really available.");
}
var instance = LocalDb.GetInstance(LocalDbInstanceName);
if (instance == null)
{
throw new InvalidOperationException(
$"Failed to get LocalDb instance {LocalDbInstanceName}, assuming LocalDb is not really available.");
}
instance.CreateDatabase(dbName, dbFilePath);
return instance;
}
public static void Cleanup()
{
var instance = LocalDb.GetInstance(LocalDbInstanceName);
if (instance != null)
{
instance.DropDatabases();
}
}
}
}

View File

@@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NUnit.Framework;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Composing.LightInject;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Scoping;
using Umbraco.Core.Strings;
using Umbraco.Tests.Common.Builders;
using Umbraco.Tests.Integration.Extensions;
using Umbraco.Tests.Integration.Implementations;
using Umbraco.Tests.Testing;
using Umbraco.Web.BackOffice.AspNetCore;
namespace Umbraco.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>
[SingleThreaded]
[NonParallelizable]
public abstract class UmbracoIntegrationTest
{
public static LightInjectContainer GetUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory)
{
var container = UmbracoServiceProviderFactory.CreateServiceContainer();
serviceProviderFactory = new UmbracoServiceProviderFactory(container);
var umbracoContainer = serviceProviderFactory.GetContainer();
return umbracoContainer;
}
/// <summary>
/// Get or create an instance of <see cref="LocalDbTestDatabase"/>
/// </summary>
/// <param name="filesPath"></param>
/// <param name="logger"></param>
/// <param name="globalSettings"></param>
/// <param name="dbFactory"></param>
/// <returns></returns>
/// <remarks>
/// There must only be ONE instance shared between all tests in a session
/// </remarks>
public static LocalDbTestDatabase GetOrCreate(string filesPath, ILogger logger, IGlobalSettings globalSettings, IUmbracoDatabaseFactory dbFactory)
{
lock (_dbLocker)
{
if (_dbInstance != null) return _dbInstance;
var localDb = new LocalDb();
if (localDb.IsAvailable == false)
throw new InvalidOperationException("LocalDB is not available.");
_dbInstance = new LocalDbTestDatabase(logger, globalSettings, localDb, filesPath, dbFactory);
return _dbInstance;
}
}
private static readonly object _dbLocker = new object();
private static LocalDbTestDatabase _dbInstance;
private readonly List<Action> _testTeardown = new List<Action>();
private readonly List<Action> _fixtureTeardown = new List<Action>();
public void OnTestTearDown(Action tearDown)
{
_testTeardown.Add(tearDown);
}
public void OnFixtureTearDown(Action tearDown)
{
_fixtureTeardown.Add(tearDown);
}
[OneTimeTearDown]
public void FixtureTearDown()
{
// call all registered callbacks
foreach (var action in _fixtureTeardown)
{
action();
}
}
[TearDown]
public void TearDown()
{
// call all registered callbacks
foreach (var action in _testTeardown)
{
action();
}
}
[SetUp]
public async Task Setup()
{
var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory);
var testHelper = new TestHelper();
var hostBuilder = new HostBuilder()
.UseUmbraco(serviceProviderFactory)
.ConfigureServices((hostContext, services) =>
{
var webHostEnvironment = testHelper.GetWebHostEnvironment();
services.AddRequiredNetCoreServices(testHelper, webHostEnvironment);
// Add it!
services.AddUmbracoConfiguration(hostContext.Configuration);
services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly);
});
var host = await hostBuilder.StartAsync();
var app = new ApplicationBuilder(host.Services);
Services = app.ApplicationServices;
// This will create a db, install the schema and ensure the app is configured to run
app.UseTestLocalDb(Path.Combine(testHelper.CurrentAssemblyDirectory, "LocalDb"), this);
app.UseUmbracoCore();
}
#region Common services
/// <summary>
/// Returns the DI container
/// </summary>
protected IServiceProvider Services { get; private set; }
/// <summary>
/// Returns the <see cref="IScopeProvider"/>
/// </summary>
protected IScopeProvider ScopeProvider => Services.GetRequiredService<IScopeProvider>();
/// <summary>
/// Returns the <see cref="IScopeAccessor"/>
/// </summary>
protected IScopeAccessor ScopeAccessor => Services.GetRequiredService<IScopeAccessor>();
/// <summary>
/// Returns the <see cref="ILogger"/>
/// </summary>
protected ILogger Logger => Services.GetRequiredService<ILogger>();
protected AppCaches AppCaches => Services.GetRequiredService<AppCaches>();
protected IIOHelper IOHelper => Services.GetRequiredService<IIOHelper>();
protected IShortStringHelper ShortStringHelper => Services.GetRequiredService<IShortStringHelper>();
protected IGlobalSettings GlobalSettings => Services.GetRequiredService<IGlobalSettings>();
protected IMapperCollection Mappers => Services.GetRequiredService<IMapperCollection>();
#endregion
#region Builders
protected GlobalSettingsBuilder GlobalSettingsBuilder = new GlobalSettingsBuilder();
protected UserBuilder UserBuilder = new UserBuilder();
protected UserGroupBuilder UserGroupBuilder = new UserGroupBuilder();
#endregion
}
}

View File

@@ -72,7 +72,7 @@ namespace Umbraco.Tests.Persistence
}
// re-create the database factory and database context with proper connection string
_databaseFactory = new UmbracoDatabaseFactory(connString, Constants.DbProviderNames.SqlCe, _logger, new Lazy<IMapperCollection>(() => Mock.Of<IMapperCollection>()), TestHelper.DbProviderFactoryCreator);
_databaseFactory = new UmbracoDatabaseFactory(_logger, connString, Constants.DbProviderNames.SqlCe, new Lazy<IMapperCollection>(() => Mock.Of<IMapperCollection>()), TestHelper.DbProviderFactoryCreator);
// test get database type (requires an actual database)
using (var database = _databaseFactory.CreateDatabase())

View File

@@ -20,7 +20,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling
{
const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=x;password=umbraco";
const string providerName = Constants.DbProviderNames.SqlServer;
var factory = new UmbracoDatabaseFactory(connectionString, providerName, Mock.Of<ILogger>(), new Lazy<IMapperCollection>(() => Mock.Of<IMapperCollection>()), TestHelper.DbProviderFactoryCreator);
var factory = new UmbracoDatabaseFactory(Mock.Of<ILogger>(), connectionString, providerName, new Lazy<IMapperCollection>(() => Mock.Of<IMapperCollection>()), TestHelper.DbProviderFactoryCreator);
using (var database = factory.CreateDatabase())
{
@@ -34,7 +34,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling
{
const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco";
const string providerName = Constants.DbProviderNames.SqlServer;
var factory = new UmbracoDatabaseFactory(connectionString, providerName, Mock.Of<ILogger>(), new Lazy<IMapperCollection>(() => Mock.Of<IMapperCollection>()), TestHelper.DbProviderFactoryCreator);
var factory = new UmbracoDatabaseFactory(Mock.Of<ILogger>(), connectionString, providerName, new Lazy<IMapperCollection>(() => Mock.Of<IMapperCollection>()), TestHelper.DbProviderFactoryCreator);
using (var database = factory.CreateDatabase())
{

View File

@@ -5,21 +5,20 @@ using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
using Umbraco.Core.Persistence;
using Umbraco.Core.PropertyEditors;
using System;
using Umbraco.Core.Configuration;
namespace Umbraco.Tests.Persistence.Repositories
{
// TODO: Move the remaining parts to Integration tests
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true, Logger = UmbracoTestOptions.Logger.Console)]
public class UserRepositoryTest : TestWithDatabaseBase
@@ -78,72 +77,6 @@ namespace Umbraco.Tests.Persistence.Repositories
return new UserGroupRepository(accessor, AppCaches.Disabled, Logger, ShortStringHelper);
}
[Test]
public void Can_Perform_Add_On_UserRepository()
{
// Arrange
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var user = MockedUser.CreateUser();
// Act
repository.Save(user);
// Assert
Assert.That(user.HasIdentity, Is.True);
}
}
[Test]
public void Can_Perform_Multiple_Adds_On_UserRepository()
{
// Arrange
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var user1 = MockedUser.CreateUser("1");
var use2 = MockedUser.CreateUser("2");
// Act
repository.Save(user1);
repository.Save(use2);
// Assert
Assert.That(user1.HasIdentity, Is.True);
Assert.That(use2.HasIdentity, Is.True);
}
}
[Test]
public void Can_Verify_Fresh_Entity_Is_Not_Dirty()
{
// Arrange
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var user = MockedUser.CreateUser();
repository.Save(user);
// Act
var resolved = repository.Get((int)user.Id);
bool dirty = ((User)resolved).IsDirty();
// Assert
Assert.That(dirty, Is.False);
}
}
[Test]
public void Can_Perform_Update_On_UserRepository()
{
@@ -206,268 +139,6 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Can_Perform_Delete_On_UserRepository()
{
// Arrange
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var user = MockedUser.CreateUser();
// Act
repository.Save(user);
var id = user.Id;
var repository2 = new UserRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, Mock.Of<IMapperCollection>(),TestObjects.GetGlobalSettings(), Mock.Of<IUserPasswordConfiguration>());
repository2.Delete(user);
var resolved = repository2.Get((int) id);
// Assert
Assert.That(resolved, Is.Null);
}
}
[Test]
public void Can_Perform_Get_On_UserRepository()
{
// Arrange
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var userGroupRepository = CreateUserGroupRepository(provider);
var user = CreateAndCommitUserWithGroup(repository, userGroupRepository);
// Act
var updatedItem = repository.Get(user.Id);
// FIXME: this test cannot work, user has 2 sections but the way it's created,
// they don't show, so the comparison with updatedItem fails - fix!
// Assert
AssertPropertyValues(updatedItem, user);
}
}
[Test]
public void Can_Perform_GetByQuery_On_UserRepository()
{
// Arrange
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
CreateAndCommitMultipleUsers(repository);
// Act
var query = scope.SqlContext.Query<IUser>().Where(x => x.Username == "TestUser1");
var result = repository.Get(query);
// Assert
Assert.That(result.Count(), Is.GreaterThanOrEqualTo(1));
}
}
[Test]
public void Can_Perform_GetAll_By_Param_Ids_On_UserRepository()
{
// Arrange
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var users = CreateAndCommitMultipleUsers(repository);
// Act
var result = repository.GetMany((int) users[0].Id, (int) users[1].Id);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result.Any(), Is.True);
Assert.That(result.Count(), Is.EqualTo(2));
}
}
[Test]
public void Can_Perform_GetAll_On_UserRepository()
{
// Arrange
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
CreateAndCommitMultipleUsers(repository);
// Act
var result = repository.GetMany();
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result.Any(), Is.True);
Assert.That(result.Count(), Is.GreaterThanOrEqualTo(3));
}
}
[Test]
public void Can_Perform_Exists_On_UserRepository()
{
// Arrange
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var users = CreateAndCommitMultipleUsers(repository);
// Act
var exists = repository.Exists(users[0].Id);
// Assert
Assert.That(exists, Is.True);
}
}
[Test]
public void Can_Perform_Count_On_UserRepository()
{
// Arrange
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var users = CreateAndCommitMultipleUsers(repository);
// Act
var query = scope.SqlContext.Query<IUser>().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2");
var result = repository.Count(query);
// Assert
Assert.AreEqual(2, result);
}
}
[Test]
public void Can_Get_Paged_Results_By_Query_And_Filter_And_Groups()
{
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var users = CreateAndCommitMultipleUsers(repository);
var query = provider.SqlContext.Query<IUser>().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2");
try
{
scope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
scope.Database.AsUmbracoDatabase().EnableSqlCount = true;
// Act
var result = repository.GetPagedResultsByQuery(query, 0, 10, out var totalRecs, user => user.Id, Direction.Ascending,
excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias },
filter: provider.SqlContext.Query<IUser>().Where(x => x.Id > -1));
// Assert
Assert.AreEqual(2, totalRecs);
}
finally
{
scope.Database.AsUmbracoDatabase().EnableSqlTrace = false;
scope.Database.AsUmbracoDatabase().EnableSqlCount = false;
}
}
}
[Test]
public void Can_Get_Paged_Results_With_Filter_And_Groups()
{
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var users = CreateAndCommitMultipleUsers(repository);
try
{
scope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
scope.Database.AsUmbracoDatabase().EnableSqlCount = true;
// Act
var result = repository.GetPagedResultsByQuery(null, 0, 10, out var totalRecs, user => user.Id, Direction.Ascending,
includeUserGroups: new[] { Constants.Security.AdminGroupAlias, Constants.Security.SensitiveDataGroupAlias },
excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias },
filter: provider.SqlContext.Query<IUser>().Where(x => x.Id == -1));
// Assert
Assert.AreEqual(1, totalRecs);
}
finally
{
scope.Database.AsUmbracoDatabase().EnableSqlTrace = false;
scope.Database.AsUmbracoDatabase().EnableSqlCount = false;
}
}
}
[Test]
public void Can_Invalidate_SecurityStamp_On_Username_Change()
{
// Arrange
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
var userGroupRepository = CreateUserGroupRepository(provider);
var user = CreateAndCommitUserWithGroup(repository, userGroupRepository);
var originalSecurityStamp = user.SecurityStamp;
// Ensure when user generated a security stamp is present
Assert.That(user.SecurityStamp, Is.Not.Null);
Assert.That(user.SecurityStamp, Is.Not.Empty);
// Update username
user.Username = user.Username + "UPDATED";
repository.Save(user);
// Get the user
var updatedUser = repository.Get(user.Id);
// Ensure the Security Stamp is invalidated & no longer the same
Assert.AreNotEqual(originalSecurityStamp, updatedUser.SecurityStamp);
}
}
private void AssertPropertyValues(IUser updatedItem, IUser originalUser)
{
Assert.That(updatedItem.Id, Is.EqualTo(originalUser.Id));
Assert.That(updatedItem.Name, Is.EqualTo(originalUser.Name));
Assert.That(updatedItem.Language, Is.EqualTo(originalUser.Language));
Assert.That(updatedItem.IsApproved, Is.EqualTo(originalUser.IsApproved));
Assert.That(updatedItem.RawPasswordValue, Is.EqualTo(originalUser.RawPasswordValue));
Assert.That(updatedItem.IsLockedOut, Is.EqualTo(originalUser.IsLockedOut));
Assert.IsTrue(updatedItem.StartContentIds.UnsortedSequenceEqual(originalUser.StartContentIds));
Assert.IsTrue(updatedItem.StartMediaIds.UnsortedSequenceEqual(originalUser.StartMediaIds));
Assert.That(updatedItem.Email, Is.EqualTo(originalUser.Email));
Assert.That(updatedItem.Username, Is.EqualTo(originalUser.Username));
Assert.That(updatedItem.AllowedSections.Count(), Is.EqualTo(originalUser.AllowedSections.Count()));
foreach (var allowedSection in originalUser.AllowedSections)
Assert.IsTrue(updatedItem.AllowedSections.Contains(allowedSection));
}
private static User CreateAndCommitUserWithGroup(IUserRepository repository, IUserGroupRepository userGroupRepository)
{
@@ -483,15 +154,5 @@ namespace Umbraco.Tests.Persistence.Repositories
return user;
}
private IUser[] CreateAndCommitMultipleUsers(IUserRepository repository)
{
var user1 = MockedUser.CreateUser("1");
var user2 = MockedUser.CreateUser("2");
var user3 = MockedUser.CreateUser("3");
repository.Save(user1);
repository.Save(user2);
repository.Save(user3);
return new IUser[] { user1, user2, user3 };
}
}
}

View File

@@ -4,24 +4,19 @@ using System.Data;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using Moq;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Services;
using Umbraco.Tests.Common;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Security;
namespace Umbraco.Tests.TestHelpers
{

View File

@@ -244,17 +244,16 @@ namespace Umbraco.Tests.TestHelpers
// mappersBuilder.AddCore();
// var mappers = mappersBuilder.CreateCollection();
var mappers = Current.Factory.GetInstance<IMapperCollection>();
databaseFactory = new UmbracoDatabaseFactory(
Constants.System.UmbracoConnectionName,
databaseFactory = new UmbracoDatabaseFactory(logger,
SettingsForTests.GetDefaultGlobalSettings(),
new ConnectionStrings(),
logger,
Constants.System.UmbracoConnectionName,
new Lazy<IMapperCollection>(() => mappers),
TestHelper.DbProviderFactoryCreator);
}
typeFinder = typeFinder ?? new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly));
fileSystems = fileSystems ?? new FileSystems(Current.Factory, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings());
typeFinder ??= new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly));
fileSystems ??= new FileSystems(Current.Factory, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings());
var coreDebug = TestHelper.CoreDebugSettings;
var mediaFileSystem = Mock.Of<IMediaFileSystem>();
var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, coreDebug, mediaFileSystem, logger, typeFinder, NoAppCache.Instance);

View File

@@ -89,7 +89,7 @@ namespace Umbraco.Tests.TestHelpers
return TestObjects.GetDatabaseFactoryMock();
var lazyMappers = new Lazy<IMapperCollection>(f.GetInstance<IMapperCollection>);
var factory = new UmbracoDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), f.GetInstance<ILogger>(), lazyMappers, TestHelper.DbProviderFactoryCreator);
var factory = new UmbracoDatabaseFactory(f.GetInstance<ILogger>(), GetDbConnectionString(), GetDbProviderName(), lazyMappers, TestHelper.DbProviderFactoryCreator);
factory.ResetForTests();
return factory;
});

View File

@@ -462,13 +462,13 @@ namespace Umbraco.Tests.Testing
var globalSettings = TestHelper.GetConfigs().Global();
var connectionStrings = TestHelper.GetConfigs().ConnectionStrings();
Composition.RegisterUnique<IUmbracoDatabaseFactory>(f => new UmbracoDatabaseFactory(
Constants.System.UmbracoConnectionName,
Composition.RegisterUnique<IUmbracoDatabaseFactory>(f => new UmbracoDatabaseFactory(Logger,
globalSettings,
connectionStrings,
Logger,
Constants.System.UmbracoConnectionName,
new Lazy<IMapperCollection>(f.GetInstance<IMapperCollection>),
TestHelper.DbProviderFactoryCreator));
Composition.RegisterUnique(f => f.TryGetInstance<IUmbracoDatabaseFactory>().SqlContext);
Composition.WithCollectionBuilder<UrlSegmentProviderCollectionBuilder>(); // empty

View File

@@ -1,39 +0,0 @@
namespace Umbraco.Tests.Testing
{
public static class UmbracoTestOptions
{
public enum Logger
{
// pure mocks
Mock,
// Serilog for tests
Serilog,
// console logger
Console
}
public enum Database
{
// no database
None,
// new empty database file for the entire feature
NewEmptyPerFixture,
// new empty database file per test
NewEmptyPerTest,
// new database file with schema for the entire feature
NewSchemaPerFixture,
// new database file with schema per test
NewSchemaPerTest
}
public enum TypeLoader
{
// the default, global type loader for tests
Default,
// create one type loader for the feature
PerFixture,
// create one type loader for each test
PerTest
}
}
}

View File

@@ -148,6 +148,7 @@
<Compile Include="Persistence\Mappers\MapperTestBase.cs" />
<Compile Include="Persistence\Repositories\DocumentRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\EntityRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\UserRepositoryTest.cs" />
<Compile Include="UmbracoExamine\ExamineExtensions.cs" />
<Compile Include="PropertyEditors\DataValueReferenceFactoryCollectionTests.cs" />
<Compile Include="PublishedContent\NuCacheChildrenTests.cs" />
@@ -231,11 +232,8 @@
<Compile Include="Persistence\NPocoTests\NPocoSqlExtensionsTests.cs" />
<Compile Include="Persistence\UnitOfWorkTests.cs" />
<Compile Include="Persistence\Repositories\RedirectUrlRepositoryTests.cs" />
<Compile Include="Testing\TestOptionAttributeBase.cs" />
<Compile Include="Testing\UmbracoTestAttribute.cs" />
<Compile Include="Testing\UmbracoTestBase.cs" />
<Compile Include="TestHelpers\Entities\MockedPropertyTypes.cs" />
<Compile Include="Testing\UmbracoTestOptions.cs" />
<Compile Include="CoreThings\TryConvertToTests.cs" />
<Compile Include="TestHelpers\TestObjects-Mocks.cs" />
<Compile Include="TestHelpers\TestObjects.cs" />
@@ -267,7 +265,6 @@
<Compile Include="Web\ModelStateExtensionsTests.cs" />
<Compile Include="Web\Mvc\RenderIndexActionSelectorAttributeTests.cs" />
<Compile Include="Persistence\NPocoTests\PetaPocoCachesTest.cs" />
<Compile Include="Persistence\Repositories\AuditRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\DomainRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\PartialViewRepositoryTests.cs" />
<Compile Include="Persistence\Repositories\PublicAccessRepositoryTest.cs" />
@@ -344,7 +341,6 @@
<Compile Include="Persistence\Repositories\TagRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\NotificationsRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\TemplateRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\UserRepositoryTest.cs" />
<Compile Include="Models\Mapping\ContentWebModelMappingTests.cs" />
<Compile Include="PropertyEditors\ImageCropperTest.cs" />
<Compile Include="PublishedContent\PublishedContentExtensionTests.cs" />