using System; using System.Collections.Generic; using System.Configuration; using System.Data.SqlServerCe; using System.Linq; using System.Threading; using System.Web.Routing; using System.Xml; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; 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.PublishedCache.XmlPublishedCache; using Umbraco.Web.Security; using Umbraco.Web.Routing; using File = System.IO.File; using Umbraco.Core.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.Tests.Testing.Objects.Accessors; 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 ServiceContext ServiceContext => Current.Services; internal ScopeProvider ScopeProvider => Current.ScopeProvider as ScopeProvider; protected ISqlContext SqlContext => Container.GetInstance(); public override void SetUp() { base.SetUp(); var path = TestHelper.CurrentAssemblyDirectory; AppDomain.CurrentDomain.SetData("DataDirectory", path); } protected override void Compose() { base.Compose(); Container.Register(); Container.Register(factory => PublishedSnapshotService); Container.Register(factory => DefaultCultureAccessor); Container.GetInstance() .Clear() .Add(f => f.GetInstance().GetDataEditors()); Container.RegisterSingleton(f => { if (Options.Database == UmbracoTestOptions.Database.None) return TestObjects.GetDatabaseFactoryMock(); var sqlSyntaxProviders = new[] { new SqlCeSyntaxProvider() }; var logger = f.GetInstance(); var mappers = f.GetInstance(); var factory = new UmbracoDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), sqlSyntaxProviders, logger, mappers); factory.ResetForTests(); return factory; }); } [OneTimeTearDown] public void FixtureTearDown() { RemoveDatabaseFile(); } public override void TearDown() { var profilingLogger = Container.TryGetInstance(); 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! } // ensure the configuration matches the current version for tests var globalSettingsMock = Mock.Get(TestObjects.GetGlobalSettings()); //this will modify the IGlobalSettings instance stored in the container globalSettingsMock.Setup(x => x.ConfigurationStatus).Returns(UmbracoVersion.Current.ToString(3)); SettingsForTests.ConfigureSettings(globalSettingsMock.Object); 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;"; } protected FakeHttpContextFactory GetHttpContextFactory(string url, RouteData routeData = null) { var factory = routeData != null ? new FakeHttpContextFactory(url, routeData) : new FakeHttpContextFactory(url); return factory; } /// /// Creates the SqlCe database if required /// protected virtual void CreateSqlCeDatabase() { if (Options.Database == UmbracoTestOptions.Database.None) return; var path = TestHelper.CurrentAssemblyDirectory; //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() { var cache = NullCacheProvider.Instance; ContentTypesCache = new PublishedContentTypeCache( Container.GetInstance(), Container.GetInstance(), Container.GetInstance(), Container.GetInstance(), Logger); // 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 PublishedSnapshotService( ServiceContext, Container.GetInstance(), ScopeProvider, cache, publishedSnapshotAccessor, variationContextAccessor, Container.GetInstance(), Container.GetInstance(), Container.GetInstance(), DefaultCultureAccessor, Logger, Container.GetInstance(), new SiteDomainHelper(), 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, Logger); //Create the umbraco database and its base data schemaHelper.InitializeDatabaseSchema(); 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.CurrentAssemblyDirectory; try { var filePath = string.Concat(path, "\\UmbracoNPocoTests.sdf"); if (File.Exists(filePath)) File.Delete(filePath); } catch (Exception ex) { Logger.Error(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 UmbracoContext GetUmbracoContext(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable urlProviders = null, IGlobalSettings globalSettings = null, IPublishedSnapshotService snapshotService = null) { // ensure we have a PublishedCachesService var service = snapshotService ?? PublishedSnapshotService as PublishedSnapshotService; if (service == null) throw new Exception("Not a proper XmlPublishedCache.PublishedCachesService."); if (service is PublishedSnapshotService) { // re-initialize PublishedCacheService content with an Xml source with proper template id ((PublishedSnapshotService)service).XmlStore.GetXmlDocument = () => { var doc = new XmlDocument(); doc.LoadXml(GetXmlContent(templateId)); return doc; }; } var httpContext = GetHttpContextFactory(url, routeData).HttpContext; var umbracoContext = new UmbracoContext( httpContext, service, new WebSecurity(httpContext, Container.GetInstance(), Container.GetInstance()), umbracoSettings ?? Container.GetInstance(), urlProviders ?? Enumerable.Empty(), globalSettings ?? Container.GetInstance(), new TestVariationContextAccessor()); if (setSingleton) Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext; return umbracoContext; } protected virtual string GetXmlContent(int templateId) { return @" ]> 1 This is some content]]> "; } } }