using System; using System.Configuration; using System.Data.SqlServerCe; using System.Threading; using System.Web.Routing; using System.Xml; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using File = System.IO.File; using Umbraco.Web.Composing; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Scoping; using Umbraco.Tests.Testing; using Umbraco.Core.Migrations.Install; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Repositories; using Umbraco.Persistance.SqlCe; using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Web.WebApi; using Umbraco.Tests.Common; using Umbraco.Core.Security; namespace Umbraco.Tests.TestHelpers { /// /// Provides a base class for all Umbraco tests that require a database. /// /// /// Can provide a SqlCE database populated with the Umbraco schema. The database should /// be accessed through the UmbracoDatabaseFactory. /// Provides an Umbraco context and Xml content. /// fixme what else? /// [Apartment(ApartmentState.STA)] // why? [UmbracoTest(WithApplication = true)] public abstract class TestWithDatabaseBase : UmbracoTestBase { private string _databasePath; private static byte[] _databaseBytes; protected PublishedContentTypeCache ContentTypesCache { get; private set; } protected override ISqlSyntaxProvider SqlSyntax => GetSyntaxProvider(); protected IVariationContextAccessor VariationContextAccessor => new TestVariationContextAccessor(); internal ScopeProvider ScopeProvider => Current.ScopeProvider as ScopeProvider; protected ISqlContext SqlContext => Factory.GetRequiredService(); public override void SetUp() { // Ensure the data directory is set before continuing var path = TestHelper.WorkingDirectory; AppDomain.CurrentDomain.SetData("DataDirectory", path); base.SetUp(); } protected override void Compose() { base.Compose(); Builder.Services.AddTransient(); Builder.Services.AddTransient(factory => PublishedSnapshotService); Builder.Services.AddTransient(factory => DefaultCultureAccessor); Builder.WithCollectionBuilder() .Clear() .Add(() => Builder.TypeLoader.GetDataEditors()); Builder.WithCollectionBuilder() .Add(Builder.TypeLoader.GetUmbracoApiControllers()); Builder.Services.AddUnique(f => { if (Options.Database == UmbracoTestOptions.Database.None) return TestObjects.GetDatabaseFactoryMock(); var lazyMappers = new Lazy(f.GetRequiredService); var factory = new UmbracoDatabaseFactory(f.GetRequiredService>(), f.GetRequiredService(), GetDbConnectionString(), GetDbProviderName(), lazyMappers, TestHelper.DbProviderFactoryCreator); factory.ResetForTests(); return factory; }); } [OneTimeTearDown] public void FixtureTearDown() { RemoveDatabaseFile(); } public override void TearDown() { var profilingLogger = Factory.GetService(); var timer = profilingLogger?.TraceDuration("teardown"); // FIXME: move that one up try { // FIXME: should we first kill all scopes? if (Options.Database == UmbracoTestOptions.Database.NewSchemaPerTest) RemoveDatabaseFile(); AppDomain.CurrentDomain.SetData("DataDirectory", null); // make sure we dispose of the service to unbind events PublishedSnapshotService?.Dispose(); PublishedSnapshotService = null; } finally { timer?.Dispose(); } base.TearDown(); } private void CreateAndInitializeDatabase() { using (ProfilingLogger.TraceDuration("Create database.")) { CreateSqlCeDatabase(); // TODO: faster! } using (ProfilingLogger.TraceDuration("Initialize database.")) { InitializeDatabase(); // TODO: faster! } } protected virtual ISqlSyntaxProvider GetSyntaxProvider() { return new SqlCeSyntaxProvider(); } protected virtual string GetDbProviderName() { return Constants.DbProviderNames.SqlCe; } protected virtual string GetDbConnectionString() { return @"DataSource=|DataDirectory|UmbracoNPocoTests.sdf;Flush Interval=1;"; } /// /// Creates the SqlCe database if required /// protected virtual void CreateSqlCeDatabase() { if (Options.Database == UmbracoTestOptions.Database.None) return; var path = TestHelper.WorkingDirectory; //Get the connectionstring settings from config var settings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; ConfigurationManager.AppSettings.Set( Constants.System.UmbracoConnectionName, GetDbConnectionString()); _databasePath = string.Concat(path, "\\UmbracoNPocoTests.sdf"); //create a new database file if // - is the first test in the session // - the database file doesn't exist // - NewDbFileAndSchemaPerTest // - _isFirstTestInFixture + DbInitBehavior.NewDbFileAndSchemaPerFixture //if this is the first test in the session, always ensure a new db file is created if (FirstTestInSession || File.Exists(_databasePath) == false || Options.Database == UmbracoTestOptions.Database.NewSchemaPerTest || Options.Database == UmbracoTestOptions.Database.NewEmptyPerTest || (FirstTestInFixture && Options.Database == UmbracoTestOptions.Database.NewSchemaPerFixture)) { using (ProfilingLogger.TraceDuration("Remove database file")) { RemoveDatabaseFile(null, ex => { //if this doesn't work we have to make sure everything is reset! otherwise // well run into issues because we've already set some things up TearDown(); throw ex; }); } //Create the Sql CE database using (ProfilingLogger.TraceDuration("Create database file")) { if (Options.Database != UmbracoTestOptions.Database.NewEmptyPerTest && _databaseBytes != null) { File.WriteAllBytes(_databasePath, _databaseBytes); } else { using (var engine = new SqlCeEngine(settings.ConnectionString)) { engine.CreateDatabase(); } } } } } protected IDefaultCultureAccessor DefaultCultureAccessor { get; set; } protected IPublishedSnapshotService PublishedSnapshotService { get; set; } protected override void Initialize() // FIXME: should NOT be here! { base.Initialize(); DefaultCultureAccessor = new TestDefaultCultureAccessor(); CreateAndInitializeDatabase(); // ensure we have a PublishedSnapshotService if (PublishedSnapshotService == null) { PublishedSnapshotService = CreatePublishedSnapshotService(); } } protected virtual IPublishedSnapshotService CreatePublishedSnapshotService(GlobalSettings globalSettings = null) { var cache = NoAppCache.Instance; ContentTypesCache ??= new PublishedContentTypeCache( Factory.GetRequiredService(), Factory.GetRequiredService(), Factory.GetRequiredService(), Factory.GetRequiredService(), Factory.GetRequiredService>()); // testing=true so XmlStore will not use the file nor the database var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor); var variationContextAccessor = new TestVariationContextAccessor(); var service = new XmlPublishedSnapshotService( ServiceContext, Factory.GetRequiredService(), ScopeProvider, cache, publishedSnapshotAccessor, variationContextAccessor, Factory.GetRequiredService(), Factory.GetRequiredService(), Factory.GetRequiredService(), Factory.GetRequiredService(), DefaultCultureAccessor, Factory.GetRequiredService(), globalSettings ?? TestObjects.GetGlobalSettings(), HostingEnvironment, HostingLifetime, ShortStringHelper, new SiteDomainHelper(), Factory.GetRequiredService(), ContentTypesCache, null, true, Options.PublishedRepositoryEvents); // initialize PublishedCacheService content with an Xml source service.XmlStore.GetXmlDocument = () => { var doc = new XmlDocument(); doc.LoadXml(GetXmlContent(0)); return doc; }; return service; } /// /// Creates the tables and data for the database /// protected virtual void InitializeDatabase() { if (Options.Database == UmbracoTestOptions.Database.None || Options.Database == UmbracoTestOptions.Database.NewEmptyPerTest) return; //create the schema and load default data if: // - is the first test in the session // - NewDbFileAndSchemaPerTest // - _isFirstTestInFixture + DbInitBehavior.NewDbFileAndSchemaPerFixture if (_databaseBytes == null && (FirstTestInSession || Options.Database == UmbracoTestOptions.Database.NewSchemaPerTest || FirstTestInFixture && Options.Database == UmbracoTestOptions.Database.NewSchemaPerFixture)) { using (var scope = ScopeProvider.CreateScope()) { var schemaHelper = new DatabaseSchemaCreator(scope.Database, LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion); //Create the umbraco database and its base data schemaHelper.InitializeDatabaseSchema(); //Special case, we need to create the xml cache tables manually since they are not part of the default //setup. //TODO: Remove this when we update all tests to use nucache schemaHelper.CreateTable(); schemaHelper.CreateTable(); scope.Complete(); } _databaseBytes = File.ReadAllBytes(_databasePath); } } // FIXME: is this needed? private void CloseDbConnections(IUmbracoDatabase database) { //Ensure that any database connections from a previous test is disposed. //This is really just double safety as its also done in the TearDown. database?.Dispose(); //SqlCeContextGuardian.CloseBackgroundConnection(); } private void RemoveDatabaseFile(IUmbracoDatabase database, Action onFail = null) { if (database != null) CloseDbConnections(database); RemoveDatabaseFile(onFail); } private void RemoveDatabaseFile(Action onFail = null) { var path = TestHelper.WorkingDirectory; try { var filePath = string.Concat(path, "\\UmbracoNPocoTests.sdf"); if (File.Exists(filePath)) File.Delete(filePath); } catch (Exception ex) { LoggerFactory.CreateLogger().LogError(ex, "Could not remove the old database file"); // swallow this exception - that's because a sub class might require further teardown logic onFail?.Invoke(ex); } } protected IUmbracoContext GetUmbracoContext(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, GlobalSettings globalSettings = null, IPublishedSnapshotService snapshotService = null) { // ensure we have a PublishedCachesService var service = snapshotService ?? PublishedSnapshotService as XmlPublishedSnapshotService; if (service == null) throw new Exception("Not a proper XmlPublishedCache.PublishedCachesService."); if (service is XmlPublishedSnapshotService) { // re-initialize PublishedCacheService content with an Xml source with proper template id ((XmlPublishedSnapshotService)service).XmlStore.GetXmlDocument = () => { var doc = new XmlDocument(); doc.LoadXml(GetXmlContent(templateId)); return doc; }; } var httpContext = GetHttpContextFactory(url, routeData).HttpContext; var httpContextAccessor = TestHelper.GetHttpContextAccessor(httpContext); var umbracoContext = new UmbracoContext( httpContextAccessor, service, Mock.Of(), globalSettings ?? new GlobalSettings(), HostingEnvironment, new TestVariationContextAccessor(), UriUtility, new AspNetCookieManager(httpContextAccessor)); if (setSingleton) Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext; return umbracoContext; } protected virtual string GetXmlContent(int templateId) { return @" ]> 1 This is some content]]> "; } } }