diff --git a/src/MccSoft.IntegreSql.EF/DatabaseInitialization/IDatabaseInitializer.cs b/src/MccSoft.IntegreSql.EF/DatabaseInitialization/IDatabaseInitializer.cs index 08c1820..bf74575 100644 --- a/src/MccSoft.IntegreSql.EF/DatabaseInitialization/IDatabaseInitializer.cs +++ b/src/MccSoft.IntegreSql.EF/DatabaseInitialization/IDatabaseInitializer.cs @@ -16,7 +16,8 @@ public interface IDatabaseInitializer : IDisposable, IUseProvider /// Connection string to a copy of template database Task CreateDatabaseGetConnectionString( DatabaseSeedingOptions databaseSeeding = null - ) where TDbContext : DbContext; + ) + where TDbContext : DbContext; /// /// Creates a template database (if not created before) using DbContext.Database.EnsureCreated and @@ -28,7 +29,8 @@ Task CreateDatabaseGetConnectionString( /// Connection string to a copy of template database string CreateDatabaseGetConnectionStringSync( DatabaseSeedingOptions databaseSeeding = null - ) where TDbContext : DbContext; + ) + where TDbContext : DbContext; /// /// Creates the DbContextOptionsBuilder for passed connection string. @@ -36,7 +38,8 @@ string CreateDatabaseGetConnectionStringSync( /// DbContextOptionsBuilder CreateDbContextOptionsBuilder( string connectionString - ) where TDbContext : DbContext; + ) + where TDbContext : DbContext; /// /// Creates the database using @@ -45,7 +48,8 @@ string connectionString /// Task> CreateDatabaseGetDbContextOptionsBuilder( DatabaseSeedingOptions seedingOptions = null - ) where TDbContext : DbContext; + ) + where TDbContext : DbContext; /// /// Creates the database using @@ -54,10 +58,12 @@ Task> CreateDatabaseGetDbContextOptionsBuild /// DbContextOptionsBuilder CreateDatabaseGetDbContextOptionsBuilderSync( DatabaseSeedingOptions seedingOptions = null - ) where TDbContext : DbContext; + ) + where TDbContext : DbContext; /// /// Returns test database to a pool. + /// You need to cleanup the data in that database yourself! /// Task ReturnDatabaseToPool(string connectionString); @@ -85,5 +91,6 @@ DbContextOptionsBuilder CreateDatabaseGetDbContextOptionsBuilderSync void UseProvider( DbContextOptionsBuilder options, DatabaseSeedingOptions databaseSeedingOptions - ) where TDbContext : DbContext; + ) + where TDbContext : DbContext; } diff --git a/src/MccSoft.IntegreSql.EF/IntegreSqlClient.cs b/src/MccSoft.IntegreSql.EF/IntegreSqlClient.cs index b39d324..346f81e 100644 --- a/src/MccSoft.IntegreSql.EF/IntegreSqlClient.cs +++ b/src/MccSoft.IntegreSql.EF/IntegreSqlClient.cs @@ -60,8 +60,8 @@ public async Task InitializeTemplate(string hash) if (response.IsSuccessStatusCode) { - return await response.Content - .ReadFromJsonAsync() + return await response + .Content.ReadFromJsonAsync() .ConfigureAwait(false); } @@ -177,6 +177,49 @@ public async Task GetTestDatabase(string hash) throw new NotImplementedException("We should never reach this point"); } + /// + /// Returns test database to a pool (which allows consequent tests to reuse this database). + /// This method (contrary to ) will tell IntegreSQL to cleanup the database. + /// + public async Task ReleaseTestDatabase(string hash, int id) + { + HttpResponseMessage response; + try + { + response = await _httpClient + .PostAsync($"templates/{hash}/tests/{id}/recreate", null) + .ConfigureAwait(false); + } + catch (HttpRequestException e) + { + throw new IntegreSqlNotRunningException(_httpClient.BaseAddress?.ToString(), e); + } + + if (response.IsSuccessStatusCode) + return; + + if (response.StatusCode == HttpStatusCode.NotFound) + { + throw new IntegreSqlTemplateNotFoundException(hash); + } + if (response.StatusCode == HttpStatusCode.ServiceUnavailable) + { + throw new IntegreSqlPostgresNotAvailableException(_httpClient.BaseAddress?.ToString()); + } + + response.EnsureSuccessStatusCode(); + throw new NotImplementedException("We should never reach this point"); + } + + /// + /// Returns test database to a pool (which allows consequent tests to reuse this database). + /// Note that you need to clean up database by yourself before returning it to the pool! + /// If you return a dirty database, consequent tests might fail! + /// If you don't want to clean it up, just don't use this function. + /// Dirty databases are automatically deleted by IntegreSQL once database number exceeds a certain limit (500 by default). + /// + /// Consider using if you want the DB to be deleted/recreated by IntegreSQL + /// public async Task ReturnTestDatabase(string hash, int id) { HttpResponseMessage response; diff --git a/src/MccSoft.IntegreSql.EF/MccSoft.IntegreSql.EF.csproj b/src/MccSoft.IntegreSql.EF/MccSoft.IntegreSql.EF.csproj index d2a3650..fa1de97 100644 --- a/src/MccSoft.IntegreSql.EF/MccSoft.IntegreSql.EF.csproj +++ b/src/MccSoft.IntegreSql.EF/MccSoft.IntegreSql.EF.csproj @@ -1,7 +1,7 @@  - 0.8.16 + 0.10.1 true MCC Soft OpenIddict extension to support Auth code flow fo built-in ASP.Net identity providers diff --git a/src/MccSoft.IntegreSql.EF/NpgsqlDatabaseInitializer.cs b/src/MccSoft.IntegreSql.EF/NpgsqlDatabaseInitializer.cs index 5e84216..d927382 100644 --- a/src/MccSoft.IntegreSql.EF/NpgsqlDatabaseInitializer.cs +++ b/src/MccSoft.IntegreSql.EF/NpgsqlDatabaseInitializer.cs @@ -32,14 +32,9 @@ public class NpgsqlDatabaseInitializer : BaseDatabaseInitializer public static bool UseMd5Hash = true; /// - /// Drop database at the end of each test (when true) or not. + /// Release database (call IntegreSQL /api/v1/templates/:hash/tests/:id/recreate) at the end of each test (when true) or not. /// - /// Old databases will be reused by IntegreSQL automatically. - /// Previously we were removing databases by default, which actually interfere with IntegreSQL. - /// It was 'hanging' when tried to reuse the removed databases. - /// However, it was reported that if not dropped, Postgres starts to consume a lot of RAM. - /// So one might be willing to drop anyway - /// (though, for the latter case I'd recommend reducing the `INTEGRESQL_TEST_MAX_POOL_SIZE` in docker). + /// This API is available starting from IntegreSQL v1.1 /// public bool DropDatabaseOnRemove { get; set; } @@ -195,12 +190,12 @@ public override async Task RemoveDatabase(string connectionString) { if (DropDatabaseOnRemove) { - await using var connection = new NpgsqlConnection(connectionString); - string database = connection.Database; - connection.Open(); - connection.ChangeDatabase("postgres"); - var command = new NpgsqlCommand($"DROP DATABASE \"{database}\"", connection); - command.ExecuteNonQuery(); + var connectionStringInfo = ConnectionStringInfos[connectionString]; + + await _integreSqlClient.ReturnTestDatabase( + connectionStringInfo.Hash, + connectionStringInfo.Id + ); } else { diff --git a/tests/ExampleWeb.IntegrationTests/IntegrationTestBase.cs b/tests/ExampleWeb.IntegrationTests/IntegrationTestBase.cs index 379af94..58f3a56 100644 --- a/tests/ExampleWeb.IntegrationTests/IntegrationTestBase.cs +++ b/tests/ExampleWeb.IntegrationTests/IntegrationTestBase.cs @@ -24,32 +24,28 @@ protected IntegrationTestBase(DatabaseType databaseType) new DatabaseSeedingOptions(Name: "Integration") ); - var webAppFactory = new WebApplicationFactory().WithWebHostBuilder( - builder => + var webAppFactory = new WebApplicationFactory().WithWebHostBuilder(builder => + { + builder.ConfigureAppConfiguration( + (context, configuration) => + { + configuration.AddInMemoryCollection( + new KeyValuePair[] { new("DisableSeed", "true") } + ); + } + ); + builder.ConfigureServices(services => { - builder.ConfigureAppConfiguration( - (context, configuration) => - { - configuration.AddInMemoryCollection( - new KeyValuePair[] { new("DisableSeed", "true") } - ); - } + var descriptor = services.Single(d => + d.ServiceType == typeof(DbContextOptions) ); - builder.ConfigureServices( - services => - { - var descriptor = services.Single( - d => d.ServiceType == typeof(DbContextOptions) - ); - services.Remove(descriptor); + services.Remove(descriptor); - services.AddDbContext( - options => _databaseInitializer.UseProvider(options, _connectionString) - ); - } + services.AddDbContext(options => + _databaseInitializer.UseProvider(options, _connectionString) ); - } - ); + }); + }); _httpClient = webAppFactory.CreateDefaultClient(); } @@ -59,11 +55,14 @@ private IDatabaseInitializer CreateDatabaseInitializer(DatabaseType databaseType return databaseType switch { DatabaseType.Postgres - => new NpgsqlDatabaseInitializer( - // This is needed if you run tests NOT inside the container. - // 5434 is the public port number of Postgresql instance - connectionStringOverride: new() { Host = "localhost", Port = 5434, } - ), + => new NpgsqlDatabaseInitializer( + // This is needed if you run tests NOT inside the container. + // 5434 is the public port number of Postgresql instance + connectionStringOverride: new() { Host = "localhost", Port = 5434, } + ) + { + DropDatabaseOnRemove = true, + }, DatabaseType.Sqlite => new SqliteDatabaseInitializer(), _ => throw new ArgumentOutOfRangeException(nameof(databaseType), databaseType, null) }; diff --git a/tests/ExampleWeb.UnitTests/UnitTestBase.cs b/tests/ExampleWeb.UnitTests/UnitTestBase.cs index 8cb20d0..14500e9 100644 --- a/tests/ExampleWeb.UnitTests/UnitTestBase.cs +++ b/tests/ExampleWeb.UnitTests/UnitTestBase.cs @@ -30,11 +30,14 @@ public UnitTestBase( { null => null, DatabaseType.Postgres - => new NpgsqlDatabaseInitializer( - // This is needed if you run tests NOT inside the container. - // 5434 is the public port number of Postgresql instance - connectionStringOverride: new() { Host = "localhost", Port = 5434 } - ), + => new NpgsqlDatabaseInitializer( + // This is needed if you run tests NOT inside the container. + // 5434 is the public port number of Postgresql instance + connectionStringOverride: new() { Host = "localhost", Port = 5434 } + ) + { + DropDatabaseOnRemove = true, + }, DatabaseType.Sqlite => new SqliteDatabaseInitializer(), _ => throw new ArgumentOutOfRangeException(nameof(databaseType), databaseType, null) };