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.Strings; 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 DefaultDatabaseFactory. /// Provides an Umbraco context and Xml content. /// fixme what else? /// [TestFixture, RequiresSTA] public abstract class TestWithDatabaseBase : TestWithApplicationBase { private CacheHelper _disabledCacheHelper; private IFacadeService _facadeService; // 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); CreateAndInitializeDatabase(); } protected override void Compose() { base.Compose(); Container.Register(); Container.Register(factory => _facadeService); var manifestBuilder = new ManifestBuilder( new NullCacheProvider(), new ManifestParser(Logger, new DirectoryInfo(IOHelper.MapPath("~/App_Plugins")), new NullCacheProvider())); Container.Register(_ => manifestBuilder); Container.RegisterCollectionBuilder() .Add(() => Core.DI.Current.PluginManager.ResolvePropertyEditors()); Container.RegisterSingleton(f => { if (DatabaseTestBehavior == DatabaseBehavior.NoDatabasePerFixture) return TestObjects.GetDatabaseFactoryMock(); var sqlSyntaxProviders = new[] { new SqlCeSyntaxProvider() }; var factory = new DefaultDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), sqlSyntaxProviders, Logger, f.GetInstance(), 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; using (ProfilingLogger.TraceDuration("teardown")) { if (DatabaseTestBehavior == DatabaseBehavior.NewDbFileAndSchemaPerTest) RemoveDatabaseFile(Core.DI.Current.HasContainer ? Core.DI.Current.DatabaseContext.Database : null); AppDomain.CurrentDomain.SetData("DataDirectory", null); // make sure we dispose of the service to unbind events _facadeService?.Dispose(); _facadeService = null; } 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! } } // fixme implement this one way or another!!! // //protected override ApplicationContext CreateApplicationContext() //{ // var sqlSyntaxProviders = new[] { new SqlCeSyntaxProvider() }; // // create the database if required // // note: must do before instanciating the database factory else it will // // not find the database and will remain un-configured. // using (ProfilingLogger.TraceDuration("Create database.")) // { // //TODO make it faster // CreateSqlCeDatabase(); // } // // ensure the configuration matches the current version for tests // SettingsForTests.ConfigurationStatus = UmbracoVersion.Current.ToString(3); // // create the database factory - if the test does not require an actual database, // // use a mock factory; otherwise use a real factory. // IDatabaseFactory databaseFactory; // if (DatabaseTestBehavior == DatabaseBehavior.NoDatabasePerFixture) // { // databaseFactory = TestObjects.GetIDatabaseFactoryMock(); // } // else // { // var f = new DefaultDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), sqlSyntaxProviders, Logger, new TestUmbracoDatabaseAccessor(), Mappers); // f.ResetForTests(); // databaseFactory = f; // } // // so, using the above code to create a mock IDatabaseFactory if we don't have a real database // // but, that will NOT prevent _appContext from NOT being configured, because it cannot connect // // to the database to check the migrations ;-( // var evtMsgs = new TransientEventMessagesFactory(); // var databaseContext = new DatabaseContext(databaseFactory, Logger, Mock.Of(), Mock.Of()); // var repositoryFactory = Container.GetInstance(); // var serviceContext = TestObjects.GetServiceContext( // repositoryFactory, // _uowProvider = new NPocoUnitOfWorkProvider(databaseFactory, repositoryFactory), // new FileUnitOfWorkProvider(), // CacheHelper, // Logger, // evtMsgs, // Enumerable.Empty()); // // if the test does not require an actual database, or runs with an empty database, the application // // context will not be able to check the migration status in the database, so we have to force it // // to think it is configured. // var appContextMock = new Mock(databaseContext, serviceContext, CacheHelper, ProfilingLogger); // //if (DatabaseTestBehavior == DatabaseBehavior.NoDatabasePerFixture // no db at all // // || DatabaseTestBehavior == DatabaseBehavior.EmptyDbFilePerTest) // empty db // // appContextMock.Setup(x => x.IsConfigured).Returns(true); // _appContext = appContextMock.Object; // // initialize the database if required // // note: must do after creating the application context as // // it is using it // using (ProfilingLogger.TraceDuration("Initialize database.")) // { // // TODO make it faster // InitializeDatabase(_appContext); // } // // application is ready // //_appContext.IsReady = true; // return _appContext; //} protected DatabaseBehavior DatabaseTestBehavior { get { var att = GetType().GetCustomAttribute(false); return att?.Behavior ?? DatabaseBehavior.NoDatabasePerFixture; } } 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 (DatabaseTestBehavior == DatabaseBehavior.NoDatabasePerFixture) 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 || DatabaseTestBehavior == DatabaseBehavior.NewDbFileAndSchemaPerTest || DatabaseTestBehavior == DatabaseBehavior.EmptyDbFilePerTest || (_isFirstInFixture && DatabaseTestBehavior == DatabaseBehavior.NewDbFileAndSchemaPerFixture)) { 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 (DatabaseTestBehavior != DatabaseBehavior.EmptyDbFilePerTest && _databaseBytes != null) { File.WriteAllBytes(_databasePath, _databaseBytes); } else { using (var engine = new SqlCeEngine(settings.ConnectionString)) { engine.CreateDatabase(); } } } } } /// /// sets up resolvers before resolution is frozen /// protected override void MoreSetUp() { // fixme - what about if (PropertyValueConvertersResolver.HasCurrent == false) ?? Container.RegisterCollectionBuilder(); // ensure we have a FacadeService if (_facadeService == null) { var behavior = GetType().GetCustomAttribute(false); var cache = new NullCacheProvider(); var enableRepositoryEvents = behavior != null && behavior.EnableRepositoryEvents; 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, enableRepositoryEvents); // initialize PublishedCacheService content with an Xml source service.XmlStore.GetXmlDocument = () => { var doc = new XmlDocument(); doc.LoadXml(GetXmlContent(0)); return doc; }; _facadeService = service; } base.MoreSetUp(); } /// /// Creates the tables and data for the database /// protected virtual void InitializeDatabase() { if (DatabaseTestBehavior == DatabaseBehavior.NoDatabasePerFixture || DatabaseTestBehavior == DatabaseBehavior.EmptyDbFilePerTest) return; //create the schema and load default data if: // - is the first test in the session // - NewDbFileAndSchemaPerTest // - _isFirstTestInFixture + DbInitBehavior.NewDbFileAndSchemaPerFixture if (_databaseBytes == null && (_isFirstInSession || DatabaseTestBehavior == DatabaseBehavior.NewDbFileAndSchemaPerTest || (_isFirstInFixture && DatabaseTestBehavior == DatabaseBehavior.NewDbFileAndSchemaPerFixture))) { 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 = UmbracoContext.CreateContext( httpContext, service, new WebSecurity(httpContext, Core.DI.Current.Services.UserService), umbracoSettings ?? SettingsForTests.GetDefault(), urlProviders ?? Enumerable.Empty()); if (setSingleton) Umbraco.Web.Current.SetUmbracoContext(umbracoContext, true); return umbracoContext; } protected virtual string GetXmlContent(int templateId) { return @" ]> 1 This is some content]]> "; } } }