using System; using System.Collections.Generic; using System.Configuration; using System.Data.SqlServerCe; using System.IO; using System.Linq; using System.Web.Routing; using System.Xml; using Moq; using NUnit.Framework; using SQLCE4Umbraco; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; 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.Core.Plugins; using Umbraco.Web.Routing; using File = System.IO.File; using Umbraco.Core.DI; using Umbraco.Core.Events; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers.Stubs; 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? /// [TestFixture, RequiresSTA] public abstract class TestWithDatabaseBase : TestWithApplicationBase { private CacheHelper _disabledCacheHelper; private IFacadeService _facadeService; private IDisposable _databaseScope; // note: a fixture class is created once for all the tests in that fixture // these flags are used to ensure a new database file is used when appropriate private static bool _isFirstInSession = true; // first test in the entire test session private bool _isFirstInFixture = true; // first test in the test fixture private string _databasePath; private static byte[] _databaseBytes; protected CacheHelper DisabledCache => _disabledCacheHelper ?? (_disabledCacheHelper = CacheHelper.CreateDisabledCacheHelper()); protected IDatabaseUnitOfWorkProvider UowProvider => Core.DI.Current.Container.GetInstance(); protected PublishedContentTypeCache ContentTypesCache { get; private set; } protected override ISqlSyntaxProvider SqlSyntax => GetSyntaxProvider(); protected ServiceContext ServiceContext => Core.DI.Current.Services; protected DatabaseContext DatabaseContext => Core.DI.Current.DatabaseContext; 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 => _facadeService); Container.GetInstance() .Clear() .Add(f => f.GetInstance().ResolvePropertyEditors()); Container.RegisterSingleton(f => { if (Options.Database == UmbracoTestOptions.Database.None) return TestObjects.GetDatabaseFactoryMock(); var sqlSyntaxProviders = new[] { new SqlCeSyntaxProvider() }; var logger = f.GetInstance(); var umbracoDatabaseAccessor = f.GetInstance(); var mappers = f.GetInstance(); var factory = new UmbracoDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), sqlSyntaxProviders, logger, umbracoDatabaseAccessor, mappers); factory.ResetForTests(); return factory; }); } [TestFixtureTearDown] public void FixtureTearDown() { RemoveDatabaseFile(Core.DI.Current.HasContainer ? Core.DI.Current.DatabaseContext.Database : null); } public override void TearDown() { // before anything else... _isFirstInFixture = false; _isFirstInSession = false; var profilingLogger = Container.TryGetInstance(); var timer = profilingLogger?.TraceDuration("teardown"); // fixme move that one up try { if (Options.Database == UmbracoTestOptions.Database.NewSchemaPerTest) RemoveDatabaseFile(Core.DI.Current.HasContainer ? Core.DI.Current.Container.TryGetInstance()?.Database : null); AppDomain.CurrentDomain.SetData("DataDirectory", null); // make sure we dispose of the service to unbind events _facadeService?.Dispose(); _facadeService = null; } finally { timer?.Dispose(); } _databaseScope?.Dispose(); base.TearDown(); } private void CreateAndInitializeDatabase() { using (ProfilingLogger.TraceDuration("Create database.")) { CreateSqlCeDatabase(); // todo faster! } // ensure the configuration matches the current version for tests SettingsForTests.ConfigurationStatus = UmbracoVersion.Current.ToString(3); 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[Core.Configuration.GlobalSettings.UmbracoConnectionName]; ConfigurationManager.AppSettings.Set( Core.Configuration.GlobalSettings.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 (_isFirstInSession || File.Exists(_databasePath) == false || Options.Database == UmbracoTestOptions.Database.NewSchemaPerTest || Options.Database == UmbracoTestOptions.Database.NewEmptyPerTest || (_isFirstInFixture && 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 override void Initialize() // fixme - should NOT be here! { base.Initialize(); CreateAndInitializeDatabase(); // ensure we have a FacadeService if (_facadeService == null) { var cache = new NullCacheProvider(); ContentTypesCache = new PublishedContentTypeCache( Core.DI.Current.Services.ContentTypeService, Core.DI.Current.Services.MediaTypeService, Core.DI.Current.Services.MemberTypeService, Core.DI.Current.Logger); // testing=true so XmlStore will not use the file nor the database var facadeAccessor = new TestFacadeAccessor(); var service = new FacadeService( Core.DI.Current.Services, UowProvider, cache, facadeAccessor, Core.DI.Current.Logger, ContentTypesCache, null, true, Options.FacadeServiceRepositoryEvents); // initialize PublishedCacheService content with an Xml source service.XmlStore.GetXmlDocument = () => { var doc = new XmlDocument(); doc.LoadXml(GetXmlContent(0)); return doc; }; _facadeService = 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; _databaseScope = Core.DI.Current.DatabaseContext.CreateDatabaseScope(); //create the schema and load default data if: // - is the first test in the session // - NewDbFileAndSchemaPerTest // - _isFirstTestInFixture + DbInitBehavior.NewDbFileAndSchemaPerFixture if (_databaseBytes == null && (_isFirstInSession || Options.Database == UmbracoTestOptions.Database.NewSchemaPerTest || (_isFirstInFixture && Options.Database == UmbracoTestOptions.Database.NewSchemaPerFixture))) { var database = Core.DI.Current.DatabaseContext.Database; var schemaHelper = new DatabaseSchemaHelper(database, Logger); //Create the umbraco database and its base data schemaHelper.CreateDatabaseSchema(Mock.Of(), Mock.Of()); //close the connections, we're gonna read this baby in as a byte array so we don't have to re-initialize the // damn db for each test CloseDbConnections(database); _databaseBytes = File.ReadAllBytes(_databasePath); } } private void CloseDbConnections(UmbracoDatabase 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(UmbracoDatabase database, Action onFail = null) { if (database != null) CloseDbConnections(database); var path = TestHelper.CurrentAssemblyDirectory; try { var filePath = string.Concat(path, "\\UmbracoNPocoTests.sdf"); if (File.Exists(filePath)) File.Delete(filePath); } catch (Exception ex) { Core.DI.Current.Logger.Error("Could not remove the old database file", ex); // 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) { // ensure we have a PublishedCachesService var service = _facadeService as FacadeService; if (service == null) throw new Exception("Not a proper XmlPublishedCache.PublishedCachesService."); // re-initialize PublishedCacheService content with an Xml source with proper template id 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, Core.DI.Current.Services.UserService), umbracoSettings ?? SettingsForTests.GetDefault(), urlProviders ?? Enumerable.Empty()); if (setSingleton) Umbraco.Web.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext; return umbracoContext; } protected virtual string GetXmlContent(int templateId) { return @" ]> 1 This is some content]]> "; } } }