// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Data; using System.Data.Common; using System.Data.Odbc; using System.Linq; using System.Threading; using System.Threading.Tasks; using EntityFrameworkCore.Jet.FunctionalTests.TestUtilities; using EntityFrameworkCore.Jet.Data; using System.Data.OleDb; using Microsoft.EntityFrameworkCore.Infrastructure; using EntityFrameworkCore.Jet.Internal; using EntityFrameworkCore.Jet.Storage.Internal; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; using JetDatabaseCreator = EntityFrameworkCore.Jet.Storage.Internal.JetDatabaseCreator; // ReSharper disable InconsistentNaming namespace EntityFrameworkCore.Jet.FunctionalTests { // Tests are split into classes to enable parallel execution // Some combinations are skipped to reduce run time [JetCondition(JetCondition.IsNotCI)] public class JetDatabaseCreatorExistsTest : JetDatabaseCreatorTest { [ConditionalTheory] [InlineData(true, true, false)] [InlineData(false, false, false)] [InlineData(true, true, true)] [InlineData(false, false, true)] public Task Returns_false_when_database_does_not_exist(bool async, bool ambientTransaction, bool useCanConnect) { return Returns_false_when_database_does_not_exist_test(async, ambientTransaction, useCanConnect); } [ConditionalTheory] [InlineData(true, false, false)] [InlineData(false, true, false)] [InlineData(true, false, true)] [InlineData(false, true, true)] public Task Returns_false_when_database_with_filename_does_not_exist(bool async, bool ambientTransaction, bool useCanConnect) { return Returns_false_when_database_does_not_exist_test(async, ambientTransaction, useCanConnect); } private static async Task Returns_false_when_database_does_not_exist_test( bool async, bool ambientTransaction, bool useCanConnect) { using (var testDatabase = JetTestStore.Create("NonExisting")) { using (var context = new BloggingContext(testDatabase)) { var creator = GetDatabaseCreator(context); await context.Database.CreateExecutionStrategy().ExecuteAsync( async () => { using (CreateTransactionScope(ambientTransaction)) { if (useCanConnect) { Assert.False(async ? await creator.CanConnectAsync() : creator.CanConnect()); } else { Assert.False(async ? await creator.ExistsAsync() : creator.Exists()); } } }); Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); } } } [ConditionalTheory] [InlineData(true, false, false)] [InlineData(false, true, false)] [InlineData(true, false, true)] [InlineData(false, true, true)] public Task Returns_true_when_database_exists(bool async, bool ambientTransaction, bool useCanConnect) { return Returns_true_when_database_exists_test(async, ambientTransaction, useCanConnect); } [ConditionalTheory] [InlineData(true, true, false)] [InlineData(false, false, false)] [InlineData(true, true, true)] [InlineData(false, false, true)] public Task Returns_true_when_database_with_filename_exists(bool async, bool ambientTransaction, bool useCanConnect) { return Returns_true_when_database_exists_test(async, ambientTransaction, useCanConnect); } private static async Task Returns_true_when_database_exists_test(bool async, bool ambientTransaction, bool useCanConnect) { using (var testDatabase = JetTestStore.CreateInitialized("ExistingBloggingFile")) { using (var context = new BloggingContext(testDatabase)) { var creator = GetDatabaseCreator(context); await context.Database.CreateExecutionStrategy().ExecuteAsync( async () => { using (CreateTransactionScope(ambientTransaction)) { if (useCanConnect) { Assert.True(async ? await creator.CanConnectAsync() : creator.CanConnect()); } else { Assert.True(async ? await creator.ExistsAsync() : creator.Exists()); } } }); Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); } } } } [JetCondition(JetCondition.IsNotCI)] public class JetDatabaseCreatorEnsureDeletedTest : JetDatabaseCreatorTest { [ConditionalTheory] [InlineData(true, true, true)] [InlineData(false, false, true)] [InlineData(true, false, false)] [InlineData(false, true, false)] public Task Deletes_database(bool async, bool open, bool ambientTransaction) { return Delete_database_test(async, open, ambientTransaction); } [ConditionalTheory] [InlineData(true, true, false)] [InlineData(true, false, true)] [InlineData(false, true, true)] [InlineData(false, false, false)] public Task Deletes_database_with_filename(bool async, bool open, bool ambientTransaction) { return Delete_database_test(async, open, ambientTransaction); } private static async Task Delete_database_test(bool async, bool open, bool ambientTransaction) { using (var testDatabase = JetTestStore.CreateInitialized("EnsureDeleteBloggingFile")) { if (!open) { testDatabase.CloseConnection(); } using (var context = new BloggingContext(testDatabase)) { var creator = GetDatabaseCreator(context); Assert.True(async ? await creator.ExistsAsync() : creator.Exists()); await GetExecutionStrategy(testDatabase).ExecuteAsync( async () => { using (CreateTransactionScope(ambientTransaction)) { if (async) { Assert.True(await context.Database.EnsureDeletedAsync()); } else { Assert.True(context.Database.EnsureDeleted()); } } }); Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); Assert.False(async ? await creator.ExistsAsync() : creator.Exists()); Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); } } } [ConditionalTheory] [InlineData(true)] [InlineData(false)] public Task Noop_when_database_does_not_exist(bool async) { return Noop_when_database_does_not_exist_test(async); } [ConditionalTheory] [InlineData(true)] [InlineData(false)] public Task Noop_when_database_with_filename_does_not_exist(bool async) { return Noop_when_database_does_not_exist_test(async); } private static async Task Noop_when_database_does_not_exist_test(bool async) { using (var testDatabase = JetTestStore.Create("NonExisting")) { using (var context = new BloggingContext(testDatabase)) { var creator = GetDatabaseCreator(context); Assert.False(async ? await creator.ExistsAsync() : creator.Exists()); if (async) { Assert.False(await creator.EnsureDeletedAsync()); } else { Assert.False(creator.EnsureDeleted()); } Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); Assert.False(async ? await creator.ExistsAsync() : creator.Exists()); Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); } } } } [JetCondition(JetCondition.IsNotCI)] public class JetDatabaseCreatorEnsureCreatedTest : JetDatabaseCreatorTest { [ConditionalTheory] [InlineData(true, true)] [InlineData(false, false)] public Task Creates_schema_in_existing_database(bool async, bool ambientTransaction) { return Creates_schema_in_existing_database_test(async, ambientTransaction); } [ConditionalTheory] [InlineData(true, false)] [InlineData(false, true)] public Task Creates_schema_in_existing_database_with_filename(bool async, bool ambientTransaction) { return Creates_schema_in_existing_database_test(async, ambientTransaction); } private static Task Creates_schema_in_existing_database_test(bool async, bool ambientTransaction) { return Creates_physical_database_and_schema_test((true, async, ambientTransaction)); } [ConditionalTheory] [InlineData(true, false)] [InlineData(false, true)] public Task Creates_physical_database_and_schema(bool async, bool ambientTransaction) { return Creates_new_physical_database_and_schema_test(async, ambientTransaction); } [ConditionalTheory] [InlineData(true, true)] [InlineData(false, false)] public Task Creates_physical_database_with_filename_and_schema(bool async, bool ambientTransaction) { return Creates_new_physical_database_and_schema_test(async, ambientTransaction); } private static Task Creates_new_physical_database_and_schema_test(bool async, bool ambientTransaction) { return Creates_physical_database_and_schema_test((false, async, ambientTransaction)); } private static async Task Creates_physical_database_and_schema_test( (bool CreateDatabase, bool Async, bool ambientTransaction) options) { var (createDatabase, async, ambientTransaction) = options; using (var testDatabase = JetTestStore.Create("EnsureCreatedTestFile")) { using (var context = new BloggingContext(testDatabase)) { if (createDatabase) { testDatabase.Initialize(null, (Func)null); } else { testDatabase.DeleteDatabase(); } var creator = GetDatabaseCreator(context); Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); using (CreateTransactionScope(ambientTransaction)) { if (async) { Assert.True(await creator.EnsureCreatedAsync()); } else { Assert.True(creator.EnsureCreated()); } } Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); if (testDatabase.ConnectionState != ConnectionState.Open) { await testDatabase.OpenConnectionAsync(); } var tables = testDatabase.Query( "SELECT * FROM `INFORMATION_SCHEMA.TABLES` WHERE TABLE_TYPE = 'BASE TABLE'").ToList(); Assert.Single(tables); Assert.Equal("Blogs", tables.Single()); /*var dcolumns = testDatabase.ExecuteScalar<>()( "SELECT * FROM `INFORMATION_SCHEMA.COLUMNS` WHERE TABLE_NAME = 'Blogs' ORDER BY TABLE_NAME, COLUMN_NAME") .ToArray();*/ //Assert.Single(dcolumns); //TABLE_NAME + '.' + COLUMN_NAME + ' (' + DATA_TYPE + ')' /*dcolumns.Select( f => f.) Assert.Equal(14, columns.Length); Assert.Equal( new[] { "Blogs.AndChew (varbinary)", "Blogs.AndRow (timestamp)", "Blogs.Cheese (nvarchar)", "Blogs.ErMilan (int)", "Blogs.Fuse (smallint)", "Blogs.George (bit)", "Blogs.Key1 (nvarchar)", "Blogs.Key2 (varbinary)", "Blogs.NotFigTime (datetime2)", "Blogs.On (real)", "Blogs.OrNothing (float)", "Blogs.TheGu (uniqueidentifier)", "Blogs.ToEat (tinyint)", "Blogs.WayRound (bigint)" }, columns);*/ } } } [ConditionalTheory] [InlineData(true)] [InlineData(false)] public Task Noop_when_database_exists_and_has_schema(bool async) { return Noop_when_database_exists_and_has_schema_test(async); } [ConditionalTheory] [InlineData(true)] [InlineData(false)] public Task Noop_when_database_with_filename_exists_and_has_schema(bool async) { return Noop_when_database_exists_and_has_schema_test(async); } private static async Task Noop_when_database_exists_and_has_schema_test(bool async) { using (var testDatabase = JetTestStore.CreateInitialized("InitializedBloggingFile")) { using (var context = new BloggingContext(testDatabase)) { context.Database.EnsureCreatedResiliently(); if (async) { Assert.False(await context.Database.EnsureCreatedResilientlyAsync()); } else { Assert.False(context.Database.EnsureCreatedResiliently()); } Assert.Equal(ConnectionState.Closed, context.Database.GetDbConnection().State); } } } } [JetCondition(JetCondition.IsNotCI)] public class JetDatabaseCreatorHasTablesTest : JetDatabaseCreatorTest { [ConditionalTheory] [InlineData(true)] [InlineData(false)] public async Task Throws_when_database_does_not_exist(bool async) { using (var testDatabase = JetTestStore.GetOrCreate("NonExisting")) { var databaseCreator = GetDatabaseCreator(testDatabase); await databaseCreator.ExecutionStrategy.ExecuteAsync( databaseCreator, async creator => { var errorNumber = async ? (await Assert.ThrowsAnyAsync(() => creator.HasTablesAsyncBase())).ErrorCode : Assert.ThrowsAny(() => creator.HasTablesBase()).ErrorCode; Assert.NotEqual(errorNumber, 0); }); } } [ConditionalTheory] [InlineData(true, true)] [InlineData(true, false)] [InlineData(false, true)] [InlineData(false, false)] public async Task Returns_false_when_database_exists_but_has_no_tables(bool async, bool ambientTransaction) { using (var testDatabase = JetTestStore.GetOrCreateInitialized("Empty")) { var creator = GetDatabaseCreator(testDatabase); await GetExecutionStrategy(testDatabase).ExecuteAsync( async () => { using (CreateTransactionScope(ambientTransaction)) { Assert.False(async ? await creator.HasTablesAsyncBase() : creator.HasTablesBase()); } }); } } [ConditionalTheory] [InlineData(true, true)] [InlineData(true, false)] [InlineData(false, true)] [InlineData(false, false)] public async Task Returns_true_when_database_exists_and_has_any_tables(bool async, bool ambientTransaction) { using (var testDatabase = JetTestStore.GetOrCreate("ExistingTables") .InitializeJet(null, t => new BloggingContext(t), null)) { var creator = GetDatabaseCreator(testDatabase); await GetExecutionStrategy(testDatabase).ExecuteAsync( async () => { using (CreateTransactionScope(ambientTransaction)) { Assert.True(async ? await creator.HasTablesAsyncBase() : creator.HasTablesBase()); } }); } } } [JetCondition(JetCondition.IsNotCI)] public class JetDatabaseCreatorDeleteTest : JetDatabaseCreatorTest { [ConditionalTheory] [InlineData(true, true)] [InlineData(false, false)] public static async Task Deletes_database(bool async, bool ambientTransaction) { using (var testDatabase = JetTestStore.CreateInitialized("DeleteBlogging")) { testDatabase.CloseConnection(); var creator = GetDatabaseCreator(testDatabase); Assert.True(async ? await creator.ExistsAsync() : creator.Exists()); using (CreateTransactionScope(ambientTransaction)) { if (async) { await creator.DeleteAsync(); } else { creator.Delete(); } } Assert.False(async ? await creator.ExistsAsync() : creator.Exists()); } } [ConditionalTheory] [InlineData(true)] [InlineData(false)] public async Task Throws_when_database_does_not_exist(bool async) { using (var testDatabase = JetTestStore.GetOrCreate("NonExistingBlogging")) { var creator = GetDatabaseCreator(testDatabase); if (async) { await Assert.ThrowsAnyAsync(() => creator.DeleteAsync()); } else { Assert.ThrowsAny(() => creator.Delete()); } } } [ConditionalFact] public void Throws_when_no_initial_catalog() { var dataAccessProviderType = JetConnection.GetDataAccessProviderType(TestEnvironment.DefaultConnection); var dataAccessProviderFactory = JetFactory.Instance.GetDataAccessProviderFactory(dataAccessProviderType); var connectionStringBuilder = dataAccessProviderFactory.CreateConnectionStringBuilder(); connectionStringBuilder.ConnectionString = TestEnvironment.DefaultConnection; connectionStringBuilder.Remove("Initial Catalog"); var creator = GetDatabaseCreator(connectionStringBuilder.ToString()); var ex = Assert.Throws(() => creator.Delete()); Assert.Equal(JetStrings.NoInitialCatalog, ex.Message); } } [JetCondition(JetCondition.IsNotCI)] public class JetDatabaseCreatorCreateTablesTest : JetDatabaseCreatorTest { [ConditionalTheory] [InlineData(true, true)] [InlineData(false, false)] public async Task Creates_schema_in_existing_database_test(bool async, bool ambientTransaction) { using (var testDatabase = JetTestStore.GetOrCreateInitialized("ExistingBlogging" + (async ? "Async" : ""))) { using (var context = new BloggingContext(testDatabase)) { var creator = GetDatabaseCreator(context); using (CreateTransactionScope(ambientTransaction)) { if (async) { await creator.CreateTablesAsync(); } else { creator.CreateTables(); } } if (testDatabase.ConnectionState != ConnectionState.Open) { await testDatabase.OpenConnectionAsync(); } var tables = (await testDatabase.QueryAsync( "SELECT * FROM `INFORMATION_SCHEMA.TABLES` WHERE TABLE_TYPE = 'BASE TABLE'")).ToList(); Assert.Single(tables); Assert.Equal("Blogs", tables.Single()); var columns = (await testDatabase.QueryAsync( "SELECT * FROM `INFORMATION_SCHEMA.COLUMNS` WHERE TABLE_NAME = 'Blogs'")).ToList(); Assert.Equal(14, columns.Count); Assert.Contains(columns, c => c == "Blogs.Key1"); Assert.Contains(columns, c => c == "Blogs.Key2"); Assert.Contains(columns, c => c == "Blogs.Cheese"); Assert.Contains(columns, c => c == "Blogs.ErMilan"); Assert.Contains(columns, c => c == "Blogs.George"); Assert.Contains(columns, c => c == "Blogs.TheGu"); Assert.Contains(columns, c => c == "Blogs.NotFigTime"); Assert.Contains(columns, c => c == "Blogs.ToEat"); Assert.Contains(columns, c => c == "Blogs.OrNothing"); Assert.Contains(columns, c => c == "Blogs.Fuse"); Assert.Contains(columns, c => c == "Blogs.WayRound"); Assert.Contains(columns, c => c == "Blogs.On"); Assert.Contains(columns, c => c == "Blogs.AndChew"); Assert.Contains(columns, c => c == "Blogs.AndRow"); } } } [ConditionalTheory] [InlineData(true)] [InlineData(false)] public async Task Throws_if_database_does_not_exist(bool async) { using (var testDatabase = JetTestStore.GetOrCreate("NonExisting")) { var creator = GetDatabaseCreator(testDatabase); var exception = async ? await Record.ExceptionAsync(() => creator.CreateTablesAsync()) : Record.Exception(() => creator.CreateTables()); Assert.NotNull(exception); var isoledbex = exception is OleDbException; var isodbcex = exception is OdbcException; int errorNumber = 0; if (isoledbex) { errorNumber = ((OleDbException)exception).ErrorCode; } if (isodbcex) { errorNumber = ((OdbcException)exception).ErrorCode; } Assert.True(isoledbex || isodbcex); } } [ConditionalFact] public void GenerateCreateScript_works() { using (var context = new BloggingContext("Data Source=foo")) { var script = context.Database.GenerateCreateScript(); Assert.Equal( "CREATE TABLE `Blogs` (" + _eol + " `Key1` varchar(255) NOT NULL," + _eol + " `Key2` varbinary(510) NOT NULL," + _eol + " `Cheese` longchar NULL," + _eol + " `ErMilan` integer NOT NULL," + _eol + " `George` smallint NOT NULL," + _eol + " `TheGu` uniqueidentifier NOT NULL," + _eol + " `NotFigTime` datetime NOT NULL," + _eol + " `ToEat` tinyint NOT NULL," + _eol + " `OrNothing` float NOT NULL," + _eol + " `Fuse` smallint NOT NULL," + _eol + " `WayRound` bigint NOT NULL," + _eol + " `On` real NOT NULL," + _eol + " `AndChew` varbinary(max) NULL," + _eol + " `AndRow` rowversion NULL," + _eol + " CONSTRAINT `PK_Blogs` PRIMARY KEY (`Key1`, `Key2`)" + _eol + ");" + _eol + "GO" + _eol + _eol + _eol, script); } } private static readonly string _eol = Environment.NewLine; } [JetCondition(JetCondition.IsNotCI)] public class JetDatabaseCreatorCreateTest : JetDatabaseCreatorTest { [ConditionalTheory] [InlineData(true, false)] [InlineData(false, true)] public async Task Creates_physical_database_but_not_tables(bool async, bool ambientTransaction) { using (var testDatabase = JetTestStore.GetOrCreate("CreateTest")) { var creator = GetDatabaseCreator(testDatabase); creator.EnsureDeleted(); await GetExecutionStrategy(testDatabase).ExecuteAsync( async () => { using (CreateTransactionScope(ambientTransaction)) { if (async) { await creator.CreateAsync(); } else { creator.Create(); } } }); Assert.True(creator.Exists()); if (testDatabase.ConnectionState != ConnectionState.Open) { await testDatabase.OpenConnectionAsync(); } Assert.Empty( (await testDatabase.QueryAsync( "SELECT * FROM `INFORMATION_SCHEMA.TABLES` WHERE TABLE_TYPE = 'BASE TABLE'"))); } } [ConditionalTheory] [InlineData(true)] [InlineData(false)] public async Task Throws_if_database_already_exists(bool async) { using (var testDatabase = JetTestStore.GetOrCreateInitialized("ExistingBlogging")) { var creator = GetDatabaseCreator(testDatabase); var ex = async ? await Assert.ThrowsAsync(() => creator.CreateAsync()) : Assert.Throws(() => creator.Create()); //todo:check message } } } #pragma warning disable RCS1102 // Make class static. [JetCondition(JetCondition.IsNotCI)] public class JetDatabaseCreatorTest { protected static IDisposable CreateTransactionScope(bool useTransaction) => TestStore.CreateTransactionScope(useTransaction); protected static TestDatabaseCreator GetDatabaseCreator(JetTestStore testStore) => GetDatabaseCreator(testStore.ConnectionString); protected static TestDatabaseCreator GetDatabaseCreator(string connectionString) => GetDatabaseCreator(new BloggingContext(connectionString)); protected static TestDatabaseCreator GetDatabaseCreator(BloggingContext context) => (TestDatabaseCreator)context.GetService(); protected static IExecutionStrategy GetExecutionStrategy(JetTestStore testStore) => new BloggingContext(testStore).GetService().Create(); // ReSharper disable once ClassNeverInstantiated.Local private class TestJetExecutionStrategyFactory : JetExecutionStrategyFactory { public TestJetExecutionStrategyFactory(ExecutionStrategyDependencies dependencies) : base(dependencies) { } protected override IExecutionStrategy CreateDefaultStrategy(ExecutionStrategyDependencies dependencies) { return new NonRetryingExecutionStrategy(dependencies); } } private static IServiceProvider CreateServiceProvider() { return new ServiceCollection() .AddEntityFrameworkJet() .AddScoped() .AddScoped() .BuildServiceProvider(); } protected class BloggingContext : DbContext { private readonly string _connectionString; public BloggingContext(JetTestStore testStore) : this(testStore.ConnectionString) { } public BloggingContext(string connectionString) { _connectionString = connectionString; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .UseJet(_connectionString, TestEnvironment.DataAccessProviderFactory, b => b.ApplyConfiguration()) .UseInternalServiceProvider(CreateServiceProvider()); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity( b => { b.HasKey( e => new { e.Key1, e.Key2 }); b.Property(e => e.AndRow).IsConcurrencyToken().ValueGeneratedOnAddOrUpdate(); }); } public DbSet Blogs { get; set; } } public class Blog { public string Key1 { get; set; } public byte[] Key2 { get; set; } public string Cheese { get; set; } public int ErMilan { get; set; } public bool George { get; set; } public Guid TheGu { get; set; } public DateTime NotFigTime { get; set; } public byte ToEat { get; set; } public double OrNothing { get; set; } public short Fuse { get; set; } public long WayRound { get; set; } public float On { get; set; } public byte[] AndChew { get; set; } public byte[] AndRow { get; set; } } public class TestDatabaseCreator : JetDatabaseCreator { public TestDatabaseCreator( RelationalDatabaseCreatorDependencies dependencies, IJetRelationalConnection connection, IRawSqlCommandBuilder rawSqlCommandBuilder) : base(dependencies, connection, rawSqlCommandBuilder) { } public bool HasTablesBase() { return HasTables(); } public Task HasTablesAsyncBase(CancellationToken cancellationToken = default) { return HasTablesAsync(cancellationToken); } public IExecutionStrategy ExecutionStrategy => Dependencies.ExecutionStrategy; } } }