From 8708a529d68f9222411c48874bad79ee09684f2b Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 22 Jan 2021 20:17:22 -0500 Subject: [PATCH] added DatabaseInitializer + Tests --- src/BirdsiteLive.sln | 7 + .../Services/FederationService.cs | 25 +- .../Contracts/IDbInitializerDal.cs | 4 +- .../BirdsiteLive.DAL/DatabaseInitializer.cs | 46 ++++ .../BirdsiteLive.DAL.Tests.csproj | 21 ++ .../DatabaseInitializerTests.cs | 240 ++++++++++++++++++ 6 files changed, 322 insertions(+), 21 deletions(-) create mode 100644 src/DataAccessLayers/BirdsiteLive.DAL/DatabaseInitializer.cs create mode 100644 src/Tests/BirdsiteLive.DAL.Tests/BirdsiteLive.DAL.Tests.csproj create mode 100644 src/Tests/BirdsiteLive.DAL.Tests/DatabaseInitializerTests.cs diff --git a/src/BirdsiteLive.sln b/src/BirdsiteLive.sln index bf78d55..0a35bf6 100644 --- a/src/BirdsiteLive.sln +++ b/src/BirdsiteLive.sln @@ -39,6 +39,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Domain.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Pipeline.Tests", "Tests\BirdsiteLive.Pipeline.Tests\BirdsiteLive.Pipeline.Tests.csproj", "{BF51CA81-5A7A-46F8-B4FB-861C6BE59298}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.DAL.Tests", "Tests\BirdsiteLive.DAL.Tests\BirdsiteLive.DAL.Tests.csproj", "{5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -101,6 +103,10 @@ Global {BF51CA81-5A7A-46F8-B4FB-861C6BE59298}.Debug|Any CPU.Build.0 = Debug|Any CPU {BF51CA81-5A7A-46F8-B4FB-861C6BE59298}.Release|Any CPU.ActiveCfg = Release|Any CPU {BF51CA81-5A7A-46F8-B4FB-861C6BE59298}.Release|Any CPU.Build.0 = Release|Any CPU + {5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -119,6 +125,7 @@ Global {2A8CC30D-D775-47D1-9388-F72A5C32DE2A} = {DA3C160C-4811-4E26-A5AD-42B81FAF2D7C} {F544D745-89A8-4DEA-B61C-A7E6C53C1D63} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} {BF51CA81-5A7A-46F8-B4FB-861C6BE59298} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} + {5A1E3EB5-6CBB-470D-8A0D-10F8C18353D5} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {69E8DCAD-4C37-4010-858F-5F94E6FBABCE} diff --git a/src/BirdsiteLive/Services/FederationService.cs b/src/BirdsiteLive/Services/FederationService.cs index f2c2e94..6835fd4 100644 --- a/src/BirdsiteLive/Services/FederationService.cs +++ b/src/BirdsiteLive/Services/FederationService.cs @@ -1,6 +1,8 @@ using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using BirdsiteLive.DAL; using BirdsiteLive.DAL.Contracts; using BirdsiteLive.Pipeline; using Microsoft.Extensions.Hosting; @@ -9,36 +11,21 @@ namespace BirdsiteLive.Services { public class FederationService : BackgroundService { - private readonly IDbInitializerDal _dbInitializerDal; + private readonly IDatabaseInitializer _databaseInitializer; private readonly IStatusPublicationPipeline _statusPublicationPipeline; #region Ctor - public FederationService(IDbInitializerDal dbInitializerDal, IStatusPublicationPipeline statusPublicationPipeline) + public FederationService(IDatabaseInitializer databaseInitializer, IStatusPublicationPipeline statusPublicationPipeline) { - _dbInitializerDal = dbInitializerDal; + _databaseInitializer = databaseInitializer; _statusPublicationPipeline = statusPublicationPipeline; } #endregion protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - await DbInitAsync(); + await _databaseInitializer.DbInitAsync(); await _statusPublicationPipeline.ExecuteAsync(stoppingToken); } - - private async Task DbInitAsync() - { - var currentVersion = await _dbInitializerDal.GetCurrentDbVersionAsync(); - var mandatoryVersion = _dbInitializerDal.GetMandatoryDbVersion(); - - if (currentVersion == null) - { - await _dbInitializerDal.InitDbAsync(); - } - else if (currentVersion != mandatoryVersion) - { - throw new NotImplementedException(); - } - } } } \ No newline at end of file diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IDbInitializerDal.cs b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IDbInitializerDal.cs index b786386..9d7db56 100644 --- a/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IDbInitializerDal.cs +++ b/src/DataAccessLayers/BirdsiteLive.DAL/Contracts/IDbInitializerDal.cs @@ -9,7 +9,7 @@ namespace BirdsiteLive.DAL.Contracts Task GetCurrentDbVersionAsync(); Version GetMandatoryDbVersion(); Tuple[] GetMigrationPatterns(); - Task MigrateDbAsync(Version from, Version to); - Task InitDbAsync(); + Task MigrateDbAsync(Version from, Version to); + Task InitDbAsync(); } } \ No newline at end of file diff --git a/src/DataAccessLayers/BirdsiteLive.DAL/DatabaseInitializer.cs b/src/DataAccessLayers/BirdsiteLive.DAL/DatabaseInitializer.cs new file mode 100644 index 0000000..4fe24df --- /dev/null +++ b/src/DataAccessLayers/BirdsiteLive.DAL/DatabaseInitializer.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; + +namespace BirdsiteLive.DAL +{ + public interface IDatabaseInitializer + { + Task DbInitAsync(); + } + + public class DatabaseInitializer : IDatabaseInitializer + { + private readonly IDbInitializerDal _dbInitializerDal; + + #region Ctor + public DatabaseInitializer(IDbInitializerDal dbInitializerDal) + { + _dbInitializerDal = dbInitializerDal; + } + #endregion + + public async Task DbInitAsync() + { + var currentVersion = await _dbInitializerDal.GetCurrentDbVersionAsync(); + var mandatoryVersion = _dbInitializerDal.GetMandatoryDbVersion(); + + if (currentVersion == mandatoryVersion) return; + + // Init Db + var migrationPatterns = _dbInitializerDal.GetMigrationPatterns(); + if (currentVersion == null) + currentVersion = await _dbInitializerDal.InitDbAsync(); + + // Migrate Db + while (migrationPatterns.Any(x => x.Item1 == currentVersion)) + { + var migration = migrationPatterns.First(x => x.Item1 == currentVersion); + currentVersion = await _dbInitializerDal.MigrateDbAsync(migration.Item1, migration.Item2); + } + + if (currentVersion != mandatoryVersion) throw new Exception("Migrating DB failed"); + } + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.DAL.Tests/BirdsiteLive.DAL.Tests.csproj b/src/Tests/BirdsiteLive.DAL.Tests/BirdsiteLive.DAL.Tests.csproj new file mode 100644 index 0000000..0992b02 --- /dev/null +++ b/src/Tests/BirdsiteLive.DAL.Tests/BirdsiteLive.DAL.Tests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + diff --git a/src/Tests/BirdsiteLive.DAL.Tests/DatabaseInitializerTests.cs b/src/Tests/BirdsiteLive.DAL.Tests/DatabaseInitializerTests.cs new file mode 100644 index 0000000..bd847d2 --- /dev/null +++ b/src/Tests/BirdsiteLive.DAL.Tests/DatabaseInitializerTests.cs @@ -0,0 +1,240 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using BirdsiteLive.DAL.Contracts; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace BirdsiteLive.DAL.Tests +{ + [TestClass] + public class DatabaseInitializerTests + { + [TestMethod] + public async Task DbInitAsync_UpToDate_Test() + { + #region Stubs + var current = new Version(2, 3); + var mandatory = new Version(2, 3); + #endregion + + #region Mocks + var dbInitializerDal = new Mock(MockBehavior.Strict); + + dbInitializerDal + .Setup(x => x.GetCurrentDbVersionAsync()) + .ReturnsAsync(current); + + dbInitializerDal + .Setup(x => x.GetMandatoryDbVersion()) + .Returns(mandatory); + #endregion + + var dbInitializer = new DatabaseInitializer(dbInitializerDal.Object); + await dbInitializer.DbInitAsync(); + + #region Validations + dbInitializerDal.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task DbInitAsync_NoDb_Test() + { + #region Stubs + var current = (Version)null; + var mandatory = new Version(1, 0); + + var migrationPatterns = new Tuple[0]; + #endregion + + #region Mocks + var dbInitializerDal = new Mock(MockBehavior.Strict); + + dbInitializerDal + .Setup(x => x.GetCurrentDbVersionAsync()) + .ReturnsAsync(current); + + dbInitializerDal + .Setup(x => x.GetMandatoryDbVersion()) + .Returns(mandatory); + + dbInitializerDal + .Setup(x => x.InitDbAsync()) + .ReturnsAsync(new Version(1, 0)); + + dbInitializerDal + .Setup(x => x.GetMigrationPatterns()) + .Returns(migrationPatterns); + #endregion + + var dbInitializer = new DatabaseInitializer(dbInitializerDal.Object); + await dbInitializer.DbInitAsync(); + + #region Validations + dbInitializerDal.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task DbInitAsync_NoDb_Migration_Test() + { + #region Stubs + var current = (Version)null; + var mandatory = new Version(2, 3); + + var migrationPatterns = new Tuple[] + { + new Tuple(new Version(1,0), new Version(1,7)), + new Tuple(new Version(1,7), new Version(2,0)), + new Tuple(new Version(2,0), new Version(2,3)) + }; + #endregion + + #region Mocks + var dbInitializerDal = new Mock(MockBehavior.Strict); + + dbInitializerDal + .Setup(x => x.GetCurrentDbVersionAsync()) + .ReturnsAsync(current); + + dbInitializerDal + .Setup(x => x.GetMandatoryDbVersion()) + .Returns(mandatory); + + dbInitializerDal + .Setup(x => x.InitDbAsync()) + .ReturnsAsync(new Version(1, 0)); + + dbInitializerDal + .Setup(x => x.GetMigrationPatterns()) + .Returns(migrationPatterns); + + foreach (var m in migrationPatterns) + { + dbInitializerDal + .Setup(x => x.MigrateDbAsync( + It.Is(y => y == m.Item1), + It.Is(y => y == m.Item2) + )) + .ReturnsAsync(m.Item2); + } + #endregion + + var dbInitializer = new DatabaseInitializer(dbInitializerDal.Object); + await dbInitializer.DbInitAsync(); + + #region Validations + dbInitializerDal.VerifyAll(); + #endregion + } + + [TestMethod] + public async Task DbInitAsync_HasDb_Migration_Test() + { + #region Stubs + var current = new Version(1, 7); + var mandatory = new Version(2, 3); + + var migrationPatterns = new Tuple[] + { + new Tuple(new Version(1,0), new Version(1,7)), + new Tuple(new Version(1,7), new Version(2,0)), + new Tuple(new Version(2,0), new Version(2,3)) + }; + #endregion + + #region Mocks + var dbInitializerDal = new Mock(MockBehavior.Strict); + + dbInitializerDal + .Setup(x => x.GetCurrentDbVersionAsync()) + .ReturnsAsync(current); + + dbInitializerDal + .Setup(x => x.GetMandatoryDbVersion()) + .Returns(mandatory); + + dbInitializerDal + .Setup(x => x.GetMigrationPatterns()) + .Returns(migrationPatterns); + + foreach (var m in migrationPatterns.Skip(1)) + { + dbInitializerDal + .Setup(x => x.MigrateDbAsync( + It.Is(y => y == m.Item1), + It.Is(y => y == m.Item2) + )) + .ReturnsAsync(m.Item2); + } + #endregion + + var dbInitializer = new DatabaseInitializer(dbInitializerDal.Object); + await dbInitializer.DbInitAsync(); + + #region Validations + dbInitializerDal.VerifyAll(); + #endregion + } + + [TestMethod] + [ExpectedException(typeof(Exception))] + public async Task DbInitAsync_NoDb_Migration_Error_Test() + { + #region Stubs + var current = (Version)null; + var mandatory = new Version(2, 3); + + var migrationPatterns = new Tuple[] + { + new Tuple(new Version(1,0), new Version(1,7)), + new Tuple(new Version(1,7), new Version(2,0)), + new Tuple(new Version(2,0), new Version(2,2)) + }; + #endregion + + #region Mocks + var dbInitializerDal = new Mock(MockBehavior.Strict); + + dbInitializerDal + .Setup(x => x.GetCurrentDbVersionAsync()) + .ReturnsAsync(current); + + dbInitializerDal + .Setup(x => x.GetMandatoryDbVersion()) + .Returns(mandatory); + + dbInitializerDal + .Setup(x => x.InitDbAsync()) + .ReturnsAsync(new Version(1, 0)); + + dbInitializerDal + .Setup(x => x.GetMigrationPatterns()) + .Returns(migrationPatterns); + + foreach (var m in migrationPatterns) + { + dbInitializerDal + .Setup(x => x.MigrateDbAsync( + It.Is(y => y == m.Item1), + It.Is(y => y == m.Item2) + )) + .ReturnsAsync(m.Item2); + } + #endregion + + var dbInitializer = new DatabaseInitializer(dbInitializerDal.Object); + try + { + await dbInitializer.DbInitAsync(); + } + finally + { + #region Validations + dbInitializerDal.VerifyAll(); + #endregion + } + } + } +}