From fa1a933c18fa6af1b5e64146712b8bc2d64ce276 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Fri, 15 Nov 2024 10:19:19 +0100 Subject: [PATCH 01/24] Add ExternSync (base infrastructure) - Adds a task manager which executes a given list of tasks in sequence. - Adds a sync base class to execute and validate tasks. - Adds a sync context containing a source and a target database context. --- BDMS.sln | 8 ++- src/extern-sync/BDMS.ExternSync.csproj | 43 ++++++++++++++ src/extern-sync/ISyncContext.cs | 18 ++++++ src/extern-sync/ISyncTask.cs | 12 ++++ src/extern-sync/Program.cs | 34 +++++++++++ src/extern-sync/SyncContext.cs | 38 ++++++++++++ src/extern-sync/SyncContextExtensions.cs | 59 ++++++++++++++++++ src/extern-sync/SyncContextHelpers.cs | 51 ++++++++++++++++ src/extern-sync/SyncTask.cs | 66 +++++++++++++++++++++ src/extern-sync/SyncTaskManager.cs | 27 +++++++++ src/extern-sync/Tasks/CollectInformation.cs | 36 +++++++++++ 11 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 src/extern-sync/BDMS.ExternSync.csproj create mode 100644 src/extern-sync/ISyncContext.cs create mode 100644 src/extern-sync/ISyncTask.cs create mode 100644 src/extern-sync/Program.cs create mode 100644 src/extern-sync/SyncContext.cs create mode 100644 src/extern-sync/SyncContextExtensions.cs create mode 100644 src/extern-sync/SyncContextHelpers.cs create mode 100644 src/extern-sync/SyncTask.cs create mode 100644 src/extern-sync/SyncTaskManager.cs create mode 100644 src/extern-sync/Tasks/CollectInformation.cs diff --git a/BDMS.sln b/BDMS.sln index 6814ec6dd..2ca59266a 100644 --- a/BDMS.sln +++ b/BDMS.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.3.32825.248 @@ -17,6 +17,8 @@ Project("{54A90642-561A-4BB1-A94E-469ADEE60C69}") = "BDMS.Client", "src\client\B EndProject Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{2B4BC48D-B932-4CB4-B9D6-1336A9F64D79}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDMS.ExternSync", "src\extern-sync\BDMS.ExternSync.csproj", "{951E6B71-6561-4FA7-9709-5D7F4E1D0DCA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +43,10 @@ Global {2B4BC48D-B932-4CB4-B9D6-1336A9F64D79}.Debug|Any CPU.Build.0 = Debug|Any CPU {2B4BC48D-B932-4CB4-B9D6-1336A9F64D79}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B4BC48D-B932-4CB4-B9D6-1336A9F64D79}.Release|Any CPU.Build.0 = Release|Any CPU + {951E6B71-6561-4FA7-9709-5D7F4E1D0DCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {951E6B71-6561-4FA7-9709-5D7F4E1D0DCA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {951E6B71-6561-4FA7-9709-5D7F4E1D0DCA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {951E6B71-6561-4FA7-9709-5D7F4E1D0DCA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/extern-sync/BDMS.ExternSync.csproj b/src/extern-sync/BDMS.ExternSync.csproj new file mode 100644 index 000000000..7aaff74c8 --- /dev/null +++ b/src/extern-sync/BDMS.ExternSync.csproj @@ -0,0 +1,43 @@ + + + + net8.0 + enable + enable + false + true + 8.0-all + BDMS.ExternSync + GeoWerkstatt GmbH + GeoWerkstatt GmbH + $(MSBuildThisFileDirectory)/artifacts + https://github.com/swisstopo/swissgeol-boreholes-suite + https://github.com/swisstopo/swissgeol-boreholes-suite.git + git + true + Exe + + + + + + + + + + + + + <_Parameter1>$(AssemblyName).Test + + + + + + + + + + + + diff --git a/src/extern-sync/ISyncContext.cs b/src/extern-sync/ISyncContext.cs new file mode 100644 index 000000000..7221335e8 --- /dev/null +++ b/src/extern-sync/ISyncContext.cs @@ -0,0 +1,18 @@ +namespace BDMS.ExternSync; + +/// +/// Represents a boreholes sync context containing a source and +/// a target database . +/// +public interface ISyncContext +{ + /// + /// The source database context. + /// + BdmsContext Source { get; } + + /// + /// The target database context. + /// + BdmsContext Target { get; } +} diff --git a/src/extern-sync/ISyncTask.cs b/src/extern-sync/ISyncTask.cs new file mode 100644 index 000000000..13da6f558 --- /dev/null +++ b/src/extern-sync/ISyncTask.cs @@ -0,0 +1,12 @@ +namespace BDMS.ExternSync; + +/// +/// Represents a sync task that can be executed and validated. +/// +public interface ISyncTask +{ + /// + /// Executes and validates the sync task. + /// + Task ExecuteAndValidateAsync(CancellationToken cancellationToken); +} diff --git a/src/extern-sync/Program.cs b/src/extern-sync/Program.cs new file mode 100644 index 000000000..cdff9a049 --- /dev/null +++ b/src/extern-sync/Program.cs @@ -0,0 +1,34 @@ +using BDMS.ExternSync; +using BDMS.ExternSync.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using static BDMS.ExternSync.SyncContextHelpers; + +using var app = Host.CreateDefaultBuilder(args).ConfigureServices((context, services) => +{ + // Register source and target database contexts + string GetConnectionString(string name) => + context.Configuration.GetConnectionString(name) ?? + throw new InvalidOperationException($"Connection string <{name}> not found."); + + services.AddNpgsqlDataSource(GetConnectionString(SourceBdmsContextName), serviceKey: SourceBdmsContextName); + services.AddNpgsqlDataSource(GetConnectionString(TargetBdmsContextName), serviceKey: TargetBdmsContextName); + services.AddTransient(); + + // Register tasks. The order specified here is the order in which they will be executed. + services.AddScoped(); + + // Register task manager + services.AddScoped(); +}) +.Build(); + +// Execute tasks +using var scope = app.Services.CreateScope(); +using var cancellationTokenSource = new CancellationTokenSource(); + +await scope.ServiceProvider.GetRequiredService() + .ExecuteTasksAsync(cancellationTokenSource.Token) + .ConfigureAwait(false); diff --git a/src/extern-sync/SyncContext.cs b/src/extern-sync/SyncContext.cs new file mode 100644 index 000000000..41e485ae2 --- /dev/null +++ b/src/extern-sync/SyncContext.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Data.Common; +using static BDMS.ExternSync.SyncContextHelpers; + +namespace BDMS.ExternSync; + +public class SyncContext( + [FromKeyedServices(SourceBdmsContextName)] DbConnection sourceDbConnection, + [FromKeyedServices(TargetBdmsContextName)] DbConnection targetDbConnection) + : ISyncContext, IDisposable +{ + private bool disposedValue; + + /// + public BdmsContext Source { get; } = new BdmsContext(GetDbContextOptions(sourceDbConnection)); + + /// + public BdmsContext Target { get; } = new BdmsContext(GetDbContextOptions(targetDbConnection)); + + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue && disposing) + { + Source.Dispose(); + Target.Dispose(); + + disposedValue=true; + } + } + + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/src/extern-sync/SyncContextExtensions.cs b/src/extern-sync/SyncContextExtensions.cs new file mode 100644 index 000000000..5462916f3 --- /dev/null +++ b/src/extern-sync/SyncContextExtensions.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore; +using Npgsql; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; +using System.Data; +using static BDMS.ExternSync.SyncContextHelpers; + +namespace BDMS.ExternSync; + +/// +/// extension methods."/> +/// +public static class SyncContextExtensions +{ + /// + /// Sets the options for the boreholes database context. + /// + public static void SetDbContextOptions(this NpgsqlDbContextOptionsBuilder options) + { + options.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + options.UseNetTopologySuite(); + options.MigrationsHistoryTable("__EFMigrationsHistory", BoreholesDatabaseName); + } + + /// + /// Gets the for the specified . + /// + public async static Task GetDbConnectionAsync(this BdmsContext context, CancellationToken cancellationToken = default) + { + var databaseConnection = (NpgsqlConnection)context.Database.GetDbConnection(); + if (databaseConnection.State != ConnectionState.Open) + { + await databaseConnection.OpenAsync(cancellationToken).ConfigureAwait(false); + } + return databaseConnection; + } + + /// + /// Gets the database schema version for the specified . + /// + public async static Task GetDbSchemaVersionAsync(this BdmsContext context, CancellationToken cancellationToken = default) + { + var migrations = await context.Database.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false); + return migrations.LastOrDefault(); + } + + /// + /// Cleans up superfluous data in the boreholes database. After applying database migrations to a new/empty database, + /// the database contains data that is not meant to be present in the production environment. This method removes this data. + /// + public static async Task CleanUpSuperfluousDataAsync(this BdmsContext context, CancellationToken cancellationToken = default) + { + var usersToRemove = await context.Users + .Where(u => u.SubjectId.StartsWith("sub_")) + .ToListAsync(cancellationToken).ConfigureAwait(false); + + context.Users.RemoveRange(usersToRemove); + await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + } +} diff --git a/src/extern-sync/SyncContextHelpers.cs b/src/extern-sync/SyncContextHelpers.cs new file mode 100644 index 000000000..d843f6ac2 --- /dev/null +++ b/src/extern-sync/SyncContextHelpers.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore; +using Npgsql; +using System.Data.Common; + +namespace BDMS.ExternSync; + +/// +/// helper methods."/> +/// +public static class SyncContextHelpers +{ + /// + /// The name of the source . + /// + /// + /// This identifier is used to register multiple + /// services with different connection strings. + /// + public const string SourceBdmsContextName = "SourceBdmsContext"; + + /// + /// The name of the target . + /// + /// + /// This identifier is used to register multiple + /// services with different connection strings. + /// + public const string TargetBdmsContextName = "TargetBdmsContext"; + + /// + /// The name of the boreholes database. + /// + public const string BoreholesDatabaseName = "bdms"; + + /// + /// The name of the boreholes database schema. + /// + public const string BoreholesDatabaseSchemaName = "bdms"; + + /// + /// Gets the for the specified + /// + public static DbContextOptions GetDbContextOptions(DbConnection dbConnection) => + new DbContextOptionsBuilder().UseNpgsql(dbConnection, SyncContextExtensions.SetDbContextOptions).Options; + + /// + /// Gets the for the specified + /// + public static DbContextOptions GetDbContextOptions(string connectionString) => + new DbContextOptionsBuilder().UseNpgsql(connectionString, SyncContextExtensions.SetDbContextOptions).Options; +} diff --git a/src/extern-sync/SyncTask.cs b/src/extern-sync/SyncTask.cs new file mode 100644 index 000000000..f98591f5e --- /dev/null +++ b/src/extern-sync/SyncTask.cs @@ -0,0 +1,66 @@ +using Microsoft.Extensions.Logging; + +namespace BDMS.ExternSync; + +/// +/// Represents a sync task containing source and target database contexts +/// that can be executed and validated. +/// +/// The sync context. +/// The logger for this instance. +public abstract class SyncTask(ISyncContext syncContext, ILogger logger) + : IDisposable, ISyncTask +{ + private bool disposedValue; + + /// + /// The source database context. + /// + protected BdmsContext Source { get; } = syncContext.Source; + + /// + /// The target database context. + /// + protected BdmsContext Target { get; } = syncContext.Target; + + /// + /// The logger for the . + /// + protected ILogger Logger { get; } = logger; + + /// + public async Task ExecuteAndValidateAsync(CancellationToken cancellationToken) + { + await RunTaskAsync(cancellationToken).ConfigureAwait(false); + await ValidateTaskAsync(cancellationToken).ConfigureAwait(false); + } + + /// + /// Runs the . + /// + protected abstract Task RunTaskAsync(CancellationToken cancellationToken); + + /// + /// Validates the result after the has been executed. + /// + protected abstract Task ValidateTaskAsync(CancellationToken cancellationToken); + + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue && disposing) + { + Source.Dispose(); + Target.Dispose(); + + disposedValue=true; + } + } + + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/src/extern-sync/SyncTaskManager.cs b/src/extern-sync/SyncTaskManager.cs new file mode 100644 index 000000000..1637f6ff2 --- /dev/null +++ b/src/extern-sync/SyncTaskManager.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.Logging; + +namespace BDMS.ExternSync; + +/// +/// Represents a task manager that executes and validates a collection of in sequence. +/// +/// A collection of s to execute in sequence. +/// The logger for this instance. +public class SyncTaskManager(IEnumerable tasks, ILogger logger) +{ + /// + /// Executes the s in sequence." + /// + public async Task ExecuteTasksAsync(CancellationToken cancellationToken) + { + logger.LogInformation("Queued tasks: {TaskNames}", string.Join(", ", tasks.Select(t => t.GetType().Name))); + + foreach (var task in tasks) + { + logger.LogInformation("Executing task {TaskName}...", task.GetType().Name); + await task.ExecuteAndValidateAsync(cancellationToken).ConfigureAwait(false); + } + + logger.LogInformation("All tasks have been executed successfully."); + } +} diff --git a/src/extern-sync/Tasks/CollectInformation.cs b/src/extern-sync/Tasks/CollectInformation.cs new file mode 100644 index 000000000..054f205df --- /dev/null +++ b/src/extern-sync/Tasks/CollectInformation.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Logging; + +namespace BDMS.ExternSync.Tasks; + +/// +/// Collects some information about the source and target databases +/// and checks if they are not the same. +/// +public class CollectInformation(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) +{ + /// + protected override async Task RunTaskAsync(CancellationToken cancellationToken) + { + // Log the source and target database connection information + var source = await Source.GetDbConnectionAsync(cancellationToken).ConfigureAwait(false); + var target = await Target.GetDbConnectionAsync(cancellationToken).ConfigureAwait(false); + + Logger.LogInformation( + "Source database: {SourceDatabase}\nTarget database: {TargetDatabase}", + $"{source.Database}@{source.Host}:{source.Port}", + $"{target.Database}@{target.Host}:{target.Port}"); + } + + /// + protected override async Task ValidateTaskAsync(CancellationToken cancellationToken) + { + // Check if the source and target databases are the same + var source = await Source.GetDbConnectionAsync(cancellationToken).ConfigureAwait(false); + var target = await Target.GetDbConnectionAsync(cancellationToken).ConfigureAwait(false); + + if (source.Database == target.Database && source.Host == target.Host && source.Port == target.Port) + { + throw new InvalidOperationException("Source and target databases cannot be the same."); + } + } +} From b81dc123b1e3637e45578cae71133dea8a469848 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Fri, 15 Nov 2024 10:26:00 +0100 Subject: [PATCH 02/24] Add additional sync tasks for testing purposes to verify and test the CI/CD pipelines --- src/extern-sync/Program.cs | 3 ++ src/extern-sync/Tasks/MigrateDatabase.cs | 49 +++++++++++++++++++++ src/extern-sync/Tasks/SynchronizeUsers.cs | 44 +++++++++++++++++++ src/extern-sync/Tasks/UpdateSequences.cs | 52 +++++++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 src/extern-sync/Tasks/MigrateDatabase.cs create mode 100644 src/extern-sync/Tasks/SynchronizeUsers.cs create mode 100644 src/extern-sync/Tasks/UpdateSequences.cs diff --git a/src/extern-sync/Program.cs b/src/extern-sync/Program.cs index cdff9a049..b131a3dc7 100644 --- a/src/extern-sync/Program.cs +++ b/src/extern-sync/Program.cs @@ -19,6 +19,9 @@ string GetConnectionString(string name) => // Register tasks. The order specified here is the order in which they will be executed. services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); // Register task manager services.AddScoped(); diff --git a/src/extern-sync/Tasks/MigrateDatabase.cs b/src/extern-sync/Tasks/MigrateDatabase.cs new file mode 100644 index 000000000..ff434c26c --- /dev/null +++ b/src/extern-sync/Tasks/MigrateDatabase.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; + +namespace BDMS.ExternSync.Tasks; + +/// +/// Checks whether the source and target databases have the same schema version. +/// +/// +/// IMPORTANT! This class does not yet implement the actual behavior. It only +/// contains sample code to verify the testing and integration concepts. +/// +public class MigrateDatabase(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) +{ + /// + protected override async Task RunTaskAsync(CancellationToken cancellationToken) + { + // Check the target database schema version and migrate if necessary + var targetDbSchemaVersion = await Target.GetDbSchemaVersionAsync(cancellationToken).ConfigureAwait(false); + if (targetDbSchemaVersion.IsNullOrEmpty()) + { + Logger.LogInformation("The target database hat not been migrated yet.\nInitializing migration..."); + await Target.Database.MigrateAsync(cancellationToken).ConfigureAwait(false); + + Logger.LogInformation("Clean-up superfluous data..."); + await Target.CleanUpSuperfluousDataAsync(cancellationToken).ConfigureAwait(false); + } + + // Log the source and target database schema versions + Logger.LogInformation( + "Source database schema version: {SourceDatabase}\nTarget database schema version: {TargetDatabase}", + await Source.GetDbSchemaVersionAsync(cancellationToken).ConfigureAwait(false), + await Target.GetDbSchemaVersionAsync(cancellationToken).ConfigureAwait(false)); + } + + /// + protected override async Task ValidateTaskAsync(CancellationToken cancellationToken) + { + var sourceDbSchemaVersion = await Source.GetDbSchemaVersionAsync(cancellationToken).ConfigureAwait(false); + var targetDbSchemaVersion = await Target.GetDbSchemaVersionAsync(cancellationToken).ConfigureAwait(false); + if (sourceDbSchemaVersion != targetDbSchemaVersion) + { + throw new InvalidOperationException( + $"Source and target databases have different schema versions\n" + + $"Source: <{sourceDbSchemaVersion}>, Target: <{targetDbSchemaVersion}>"); + } + } +} diff --git a/src/extern-sync/Tasks/SynchronizeUsers.cs b/src/extern-sync/Tasks/SynchronizeUsers.cs new file mode 100644 index 000000000..3eff008c7 --- /dev/null +++ b/src/extern-sync/Tasks/SynchronizeUsers.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace BDMS.ExternSync.Tasks; + +/// +/// Synchronizes users from the source database to the target database. +/// +/// +/// IMPORTANT! This class does not yet implement the actual behavior. It only +/// contains sample code to verify the testing and integration concepts. +/// +public class SynchronizeUsers(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) +{ + /// + protected override async Task RunTaskAsync(CancellationToken cancellationToken) + { + var sourceUsers = await Source.Users.ToListAsync(cancellationToken).ConfigureAwait(false); + var targetUsers = await Target.Users.ToListAsync(cancellationToken).ConfigureAwait(false); + + var usersToInsert = sourceUsers + .Where(sourceUser => targetUsers.All(targetUser => targetUser.Id != sourceUser.Id)) + .ToList(); + + Target.Users.AddRange(usersToInsert); + + await Target.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + } + + /// + protected override async Task ValidateTaskAsync(CancellationToken cancellationToken) + { + // Ensure that the number of users in the source and target databases is the same + var sourceUserCount = await Source.Users.CountAsync(cancellationToken).ConfigureAwait(false); + var targetUserCount = await Target.Users.CountAsync(cancellationToken).ConfigureAwait(false); + + if (sourceUserCount != targetUserCount) + { + throw new InvalidOperationException( + $"The number of users in the source and target databases is different\n" + + $"Source: {sourceUserCount}, Target: {targetUserCount}"); + } + } +} diff --git a/src/extern-sync/Tasks/UpdateSequences.cs b/src/extern-sync/Tasks/UpdateSequences.cs new file mode 100644 index 000000000..10da0047a --- /dev/null +++ b/src/extern-sync/Tasks/UpdateSequences.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Logging; +using Npgsql; +using static BDMS.ExternSync.SyncContextHelpers; + +namespace BDMS.ExternSync.Tasks; + +/// +/// Sets the sequences in the target database. +/// +/// +/// IMPORTANT! This class does not yet implement the actual behavior. It only +/// contains sample code to verify the testing and integration concepts. +/// +public class UpdateSequences(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) +{ + private const int minValue = 20000; + private const string sequenceName = "users_id_usr_seq"; + private const string getSequenceLastValueQuery = $"SELECT last_value FROM {BoreholesDatabaseSchemaName}.{sequenceName};"; + private const string setSequenceValueQuery = $"SELECT setval('{BoreholesDatabaseSchemaName}.{sequenceName}', ($1));"; + + /// + protected override async Task RunTaskAsync(CancellationToken cancellationToken) + { + var targetDbConnection = await Target.GetDbConnectionAsync(cancellationToken).ConfigureAwait(false); + using var selectCommand = new NpgsqlCommand(getSequenceLastValueQuery, targetDbConnection); + + var lastValue = await selectCommand.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false) as long? ?? -1; + Logger.LogInformation("{SchemaName}.{SequenceName} last_value: <{LastValue}>", BoreholesDatabaseSchemaName, sequenceName, lastValue); + + if (lastValue == -1) + { + Logger.LogError("Error while reading sequence {SchemaName}.{SequenceName}.", BoreholesDatabaseSchemaName, sequenceName); + } + else if (lastValue < minValue) + { + using var alterCommand = new NpgsqlCommand(setSequenceValueQuery, targetDbConnection); + alterCommand.Parameters.AddWithValue(minValue); + + await alterCommand.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); + + Logger.LogInformation("Sequence {SchemaName}.{SequenceName} has been set to {MinValue}.", BoreholesDatabaseSchemaName, sequenceName, minValue); + } + else + { + Logger.LogInformation("Sequence for {SchemaName}.{SequenceName} has already been set.", BoreholesDatabaseSchemaName, sequenceName); + } + } + + /// + protected override async Task ValidateTaskAsync(CancellationToken cancellationToken) => + await Task.FromResult(true).ConfigureAwait(false); +} From bb1d900a5272bb750d5bcac8647796adfa5a4d96 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Fri, 15 Nov 2024 10:32:22 +0100 Subject: [PATCH 03/24] Add ExternSync tests (base infrastructure) - Adds some unit tests for the sync task manager. - Adds a test sync context with in-memory or real database contexts. --- BDMS.sln | 8 ++- tests/extern-sync/BDMS.ExternSync.Test.csproj | 49 +++++++++++++++ tests/extern-sync/SyncContext.cs | 62 +++++++++++++++++++ tests/extern-sync/SyncContextExtensions.cs | 23 +++++++ tests/extern-sync/SyncContextHelpers.cs | 44 +++++++++++++ tests/extern-sync/SyncTaskManagerTest.cs | 60 ++++++++++++++++++ .../Tasks/CollectInformationTest.cs | 18 ++++++ .../extern-sync/Tasks/MigrateDatabaseTest.cs | 18 ++++++ .../extern-sync/Tasks/SynchronizeUsersTest.cs | 30 +++++++++ .../extern-sync/Tasks/UpdateSequencesTest.cs | 26 ++++++++ 10 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 tests/extern-sync/BDMS.ExternSync.Test.csproj create mode 100644 tests/extern-sync/SyncContext.cs create mode 100644 tests/extern-sync/SyncContextExtensions.cs create mode 100644 tests/extern-sync/SyncContextHelpers.cs create mode 100644 tests/extern-sync/SyncTaskManagerTest.cs create mode 100644 tests/extern-sync/Tasks/CollectInformationTest.cs create mode 100644 tests/extern-sync/Tasks/MigrateDatabaseTest.cs create mode 100644 tests/extern-sync/Tasks/SynchronizeUsersTest.cs create mode 100644 tests/extern-sync/Tasks/UpdateSequencesTest.cs diff --git a/BDMS.sln b/BDMS.sln index 2ca59266a..6957d471a 100644 --- a/BDMS.sln +++ b/BDMS.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.3.32825.248 @@ -19,6 +19,8 @@ Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-co EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDMS.ExternSync", "src\extern-sync\BDMS.ExternSync.csproj", "{951E6B71-6561-4FA7-9709-5D7F4E1D0DCA}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDMS.ExternSync.Test", "tests\extern-sync\BDMS.ExternSync.Test.csproj", "{7874F0AF-6311-4A8A-897A-C4DD82A97E6F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -47,6 +49,10 @@ Global {951E6B71-6561-4FA7-9709-5D7F4E1D0DCA}.Debug|Any CPU.Build.0 = Debug|Any CPU {951E6B71-6561-4FA7-9709-5D7F4E1D0DCA}.Release|Any CPU.ActiveCfg = Release|Any CPU {951E6B71-6561-4FA7-9709-5D7F4E1D0DCA}.Release|Any CPU.Build.0 = Release|Any CPU + {7874F0AF-6311-4A8A-897A-C4DD82A97E6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7874F0AF-6311-4A8A-897A-C4DD82A97E6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7874F0AF-6311-4A8A-897A-C4DD82A97E6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7874F0AF-6311-4A8A-897A-C4DD82A97E6F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/tests/extern-sync/BDMS.ExternSync.Test.csproj b/tests/extern-sync/BDMS.ExternSync.Test.csproj new file mode 100644 index 000000000..eb03615ea --- /dev/null +++ b/tests/extern-sync/BDMS.ExternSync.Test.csproj @@ -0,0 +1,49 @@ + + + + net8.0 + enable + enable + + false + true + $(MSBuildProjectName.Replace(" ", "_")) + + + + + + + + + + + + + + + + + + + + + + + + + + initdb.d\01-schema.sql + PreserveNewest + + + initdb.d\02-geolcodes.sql + PreserveNewest + + + initdb.d\03-data.sql + PreserveNewest + + + + diff --git a/tests/extern-sync/SyncContext.cs b/tests/extern-sync/SyncContext.cs new file mode 100644 index 000000000..5fd161ccb --- /dev/null +++ b/tests/extern-sync/SyncContext.cs @@ -0,0 +1,62 @@ +using static BDMS.ExternSync.Test.SyncContextHelpers; + +namespace BDMS.ExternSync.Test; + +/// +/// Represents a containing a source and target +/// for testing purposes. The +/// can either use a real PostgreSQL database or an in-memory context. +/// +internal class SyncContext : ISyncContext, IDisposable +{ + private bool disposedValue; + + /// + public BdmsContext Source { get; } + + /// + public BdmsContext Target { get; } + + /// + /// Initializes a new instance of the class. + /// + private SyncContext(BdmsContext source, BdmsContext target) + { + Source = source; + Target = target; + } + + /// + /// Builds a new instance of the class. + /// + /// By default a real PostgreSQL database is used + /// when source and target database contexts are created. This allows to execute + /// raw SQL queries but comes with a performance penalty. When set to true + /// an in-memory database is used instead. + public static async Task BuildAsync(bool useInMemory = false) + { + var source = CreateDbContextAsync(useInMemory); + var target = CreateDbContextAsync(useInMemory); + await Task.WhenAll(source, target).ConfigureAwait(false); + return new SyncContext(source.Result, target.Result); + } + + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue && disposing) + { + Source.Dispose(); + Target.Dispose(); + + disposedValue=true; + } + } + + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/tests/extern-sync/SyncContextExtensions.cs b/tests/extern-sync/SyncContextExtensions.cs new file mode 100644 index 000000000..fd15ce9d2 --- /dev/null +++ b/tests/extern-sync/SyncContextExtensions.cs @@ -0,0 +1,23 @@ +using BDMS.Models; + +namespace BDMS.ExternSync.Test; + +/// +/// extension methods."/> +/// +internal static class SyncContextExtensions +{ + internal static async Task SeedUserTestDataAsync(this SyncContext syncContext) + { + var (source, target) = (syncContext.Source, syncContext.Target); + + source.Users.Add(new User { Id = 1, FirstName = "John", LastName = "Doe", Name = "John Doe", SubjectId = "doe123" }); + source.Users.Add(new User { Id = 2, FirstName = "Jane", LastName = "Doe", Name = "Jane Doe", SubjectId = "doe456" }); + source.Users.Add(new User { Id = 3, FirstName = "Alice", LastName = "Smith", Name = "Alice Smith", SubjectId = "smith789" }); + source.Users.Add(new User { Id = 4, FirstName = "Bob", LastName = "Smith", Name = "Bob Smith", SubjectId = "smith101" }); + await source.SaveChangesAsync().ConfigureAwait(false); + + target.Users.Add(new User { Id = 1, FirstName = "Charlie", LastName = "Brown", Name = "Charlie Brown", SubjectId = "brown123" }); + await target.SaveChangesAsync().ConfigureAwait(false); + } +} diff --git a/tests/extern-sync/SyncContextHelpers.cs b/tests/extern-sync/SyncContextHelpers.cs new file mode 100644 index 000000000..5f814061e --- /dev/null +++ b/tests/extern-sync/SyncContextHelpers.cs @@ -0,0 +1,44 @@ +using DotNet.Testcontainers.Builders; +using Microsoft.EntityFrameworkCore; +using Testcontainers.PostgreSql; +using static BDMS.ExternSync.SyncContextHelpers; + +namespace BDMS.ExternSync.Test; + +/// +/// helper methods."/> +/// +internal static class SyncContextHelpers +{ + /// + /// Creates a new for testing purposes. Use to specify + /// whether to use a real PostgreSQL database or an in-memory context. + /// + internal async static Task CreateDbContextAsync(bool useInMemory) => + useInMemory ? CreateInMemoryDbContext() : await CreatePostgreSqlDbContextAsync().ConfigureAwait(false); + + private static BdmsContext CreateInMemoryDbContext() => + new(new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); + + private async static Task CreatePostgreSqlDbContextAsync() + { + var postgreSqlContainer = await CreatePostgreSqlContainerAsync().ConfigureAwait(false); + var context = new BdmsContext(GetDbContextOptions(postgreSqlContainer.GetConnectionString())); + await context.Database.MigrateAsync().ConfigureAwait(false); + await context.CleanUpSuperfluousDataAsync().ConfigureAwait(false); + return context; + } + + private static async Task CreatePostgreSqlContainerAsync() + { + var initDbDirectoryPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "initdb.d")); + var postgreSqlContainer = new PostgreSqlBuilder() + .WithImage("postgis/postgis:15-3.4-alpine") + .WithDatabase(BoreholesDatabaseName) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(5432)) + .WithResourceMapping(initDbDirectoryPath, "/docker-entrypoint-initdb.d") + .Build(); + await postgreSqlContainer.StartAsync(); + return postgreSqlContainer; + } +} diff --git a/tests/extern-sync/SyncTaskManagerTest.cs b/tests/extern-sync/SyncTaskManagerTest.cs new file mode 100644 index 000000000..338d0537b --- /dev/null +++ b/tests/extern-sync/SyncTaskManagerTest.cs @@ -0,0 +1,60 @@ +using Microsoft.Extensions.Logging; +using Moq; + +namespace BDMS.ExternSync.Test; + +[TestClass] +public class SyncTaskManagerTest +{ + private List> syncTasks; + + [TestInitialize] + public void Initialize() + { + syncTasks = + [ + new Mock(MockBehavior.Strict), + new Mock(MockBehavior.Strict), + new Mock(MockBehavior.Strict) + ]; + } + + [TestCleanup] + public void Cleanup() => syncTasks.ForEach(t => t.VerifyAll()); + + [TestMethod] + public async Task ExecutesTasksInSequence() + { + var mockSequence = new MockSequence(); + syncTasks[0].InSequence(mockSequence).Setup(t => t.ExecuteAndValidateAsync(It.IsAny())).Returns(Task.CompletedTask); + syncTasks[1].InSequence(mockSequence).Setup(t => t.ExecuteAndValidateAsync(It.IsAny())).Returns(Task.CompletedTask); + syncTasks[2].InSequence(mockSequence).Setup(t => t.ExecuteAndValidateAsync(It.IsAny())).Returns(Task.CompletedTask); + + var syncTaskManager = new SyncTaskManager(syncTasks.Select(t => t.Object).ToList(), Mock.Of>()); + await syncTaskManager.ExecuteTasksAsync(Mock.Of().Token); + + syncTasks[0].Verify(t => t.ExecuteAndValidateAsync(It.IsAny()), Times.Once); + syncTasks[1].Verify(t => t.ExecuteAndValidateAsync(It.IsAny()), Times.Once); + syncTasks[2].Verify(t => t.ExecuteAndValidateAsync(It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task StopsExecutingOnFailure() + { + var mockSequence = new MockSequence(); + syncTasks[0].InSequence(mockSequence).Setup(t => t.ExecuteAndValidateAsync(It.IsAny())).Returns(Task.CompletedTask); + syncTasks[1].InSequence(mockSequence).Setup(t => t.ExecuteAndValidateAsync(It.IsAny())).ThrowsAsync(new InvalidOperationException("BLACKTOLL SQUEAKY.")); + syncTasks[2].InSequence(mockSequence).Setup(t => t.ExecuteAndValidateAsync(It.IsAny())).Returns(Task.CompletedTask); + + var syncTaskManager = new SyncTaskManager(syncTasks.Select(t => t.Object).ToList(), Mock.Of>()); + + var exception = await Assert.ThrowsExceptionAsync(() => + syncTaskManager.ExecuteTasksAsync(Mock.Of().Token)); + + Assert.AreEqual("BLACKTOLL SQUEAKY.", exception.Message); + + syncTasks[0].Verify(t => t.ExecuteAndValidateAsync(It.IsAny()), Times.Once); + syncTasks[1].Verify(t => t.ExecuteAndValidateAsync(It.IsAny()), Times.Once); + syncTasks[2].Verify(t => t.ExecuteAndValidateAsync(It.IsAny()), Times.Never); + } +} diff --git a/tests/extern-sync/Tasks/CollectInformationTest.cs b/tests/extern-sync/Tasks/CollectInformationTest.cs new file mode 100644 index 000000000..cdda52503 --- /dev/null +++ b/tests/extern-sync/Tasks/CollectInformationTest.cs @@ -0,0 +1,18 @@ +using BDMS.ExternSync.Tasks; +using Microsoft.Extensions.Logging; +using Moq; + +namespace BDMS.ExternSync.Test; + +[TestClass] +public class CollectInformationTest +{ + [TestMethod] + public async Task CollectInformation() + { + using var syncContext = await SyncContext.BuildAsync().ConfigureAwait(false); + using var syncTask = new CollectInformation(syncContext, new Mock>().Object); + + await syncTask.ExecuteAndValidateAsync(Mock.Of().Token); + } +} diff --git a/tests/extern-sync/Tasks/MigrateDatabaseTest.cs b/tests/extern-sync/Tasks/MigrateDatabaseTest.cs new file mode 100644 index 000000000..015e29d5f --- /dev/null +++ b/tests/extern-sync/Tasks/MigrateDatabaseTest.cs @@ -0,0 +1,18 @@ +using BDMS.ExternSync.Tasks; +using Microsoft.Extensions.Logging; +using Moq; + +namespace BDMS.ExternSync.Test; + +[TestClass] +public class MigrateDatabaseTest +{ + [TestMethod] + public async Task MigrateDatabase() + { + using var syncContext = await SyncContext.BuildAsync().ConfigureAwait(false); + using var syncTask = new MigrateDatabase(syncContext, new Mock>().Object); + + await syncTask.ExecuteAndValidateAsync(Mock.Of().Token); + } +} diff --git a/tests/extern-sync/Tasks/SynchronizeUsersTest.cs b/tests/extern-sync/Tasks/SynchronizeUsersTest.cs new file mode 100644 index 000000000..882226e65 --- /dev/null +++ b/tests/extern-sync/Tasks/SynchronizeUsersTest.cs @@ -0,0 +1,30 @@ +using BDMS.ExternSync.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Moq; + +namespace BDMS.ExternSync.Test; + +[TestClass] +public class SynchronizeUsersTest +{ + [TestMethod] + public async Task SynchronizeUsers() + { + using var syncContext = await SyncContext.BuildAsync(useInMemory: true).ConfigureAwait(false); + using var syncTask = new SynchronizeUsers(syncContext, Mock.Of>()); + + await syncContext.SeedUserTestDataAsync().ConfigureAwait(false); + var (source, target) = (syncContext.Source, syncContext.Target); + + Assert.AreNotEqual(source.Users.CountAsync().Result, target.Users.CountAsync().Result); + Assert.AreEqual(4, await source.Users.CountAsync()); + Assert.AreEqual(1, await target.Users.CountAsync()); + + await syncTask.ExecuteAndValidateAsync(Mock.Of().Token); + + Assert.AreEqual(source.Users.CountAsync().Result, target.Users.CountAsync().Result); + Assert.AreEqual(4, await source.Users.CountAsync()); + Assert.AreEqual(4, await target.Users.CountAsync()); + } +} diff --git a/tests/extern-sync/Tasks/UpdateSequencesTest.cs b/tests/extern-sync/Tasks/UpdateSequencesTest.cs new file mode 100644 index 000000000..6e4417d89 --- /dev/null +++ b/tests/extern-sync/Tasks/UpdateSequencesTest.cs @@ -0,0 +1,26 @@ +using BDMS.ExternSync.Tasks; +using Microsoft.Extensions.Logging; +using Moq; +using Npgsql; + +namespace BDMS.ExternSync.Test; + +[TestClass] +public class UpdateSequencesTest +{ + [TestMethod] + public async Task UpdateSequences() + { + using var syncContext = await SyncContext.BuildAsync().ConfigureAwait(false); + using var syncTask = new UpdateSequences(syncContext, new Mock>().Object); + + var targetDbConnection = await syncContext.Target.GetDbConnectionAsync().ConfigureAwait(false); + using var selectCommand = new NpgsqlCommand($"SELECT last_value FROM bdms.users_id_usr_seq;", targetDbConnection); + + Assert.AreEqual((long)8, await selectCommand.ExecuteScalarAsync()); + + await syncTask.ExecuteAndValidateAsync(Mock.Of().Token); + + Assert.AreEqual((long)20000, await selectCommand.ExecuteScalarAsync()); + } +} From 5ecdad6d6fdcc82aaf588211e189c65f57dce2f3 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Fri, 15 Nov 2024 10:03:36 +0100 Subject: [PATCH 04/24] Add boreholes test rules --- tests/.editorconfig | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/.editorconfig diff --git a/tests/.editorconfig b/tests/.editorconfig new file mode 100644 index 000000000..a2ebeeb9f --- /dev/null +++ b/tests/.editorconfig @@ -0,0 +1,12 @@ +# boreholes Test Rules +# Description: Ignores rules that are not useful in test code because we want to test these edge cases + +[*.cs] +dotnet_diagnostic.CA1001.severity = none +dotnet_diagnostic.CA1801.severity = none +dotnet_diagnostic.CA1804.severity = none +dotnet_diagnostic.CS8618.severity = none +dotnet_diagnostic.SA0001.severity = none +dotnet_diagnostic.IDE0051.severity = none +dotnet_diagnostic.IDE0060.severity = none +dotnet_diagnostic.IDE0130.severity = none From ff75d03bba1c2e28cd91d66d248ff1efdf4db03e Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Fri, 15 Nov 2024 10:19:52 +0100 Subject: [PATCH 05/24] Create extern-sync Docker image --- src/extern-sync/Dockerfile | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/extern-sync/Dockerfile diff --git a/src/extern-sync/Dockerfile b/src/extern-sync/Dockerfile new file mode 100644 index 000000000..88669188c --- /dev/null +++ b/src/extern-sync/Dockerfile @@ -0,0 +1,33 @@ +# syntax=docker/dockerfile:1.7-labs + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG VERSION +ARG REVISION +WORKDIR /src + +# Restore dependencies and tools +COPY --parents "extern-sync/BDMS.ExternSync.csproj" "api/BDMS.csproj" ./ +RUN dotnet restore "./extern-sync/BDMS.ExternSync.csproj" + +# Create optimized production build +COPY --parents "extern-sync/" "api/" ./ +RUN dotnet publish "./extern-sync/BDMS.ExternSync.csproj" \ + -c Release \ + -p:UseAppHost=false \ + -p:VersionPrefix=${VERSION} \ + -p:SourceRevisionId=${REVISION} \ + -o /app/publish + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS deploy +ENV ASPNETCORE_ENVIRONMENT=Production +WORKDIR /app + +# Set default locale +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 + +COPY --from=build /app/publish . + +# Switch to the non-root user 'app' defined in the base image +USER $APP_UID +ENTRYPOINT ["dotnet", "BDMS.ExternSync.dll"] From 7e50f88699167b2e85369c4867261805eabe595f Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Fri, 15 Nov 2024 13:27:46 +0100 Subject: [PATCH 06/24] Build and push extern-sync Docker images --- .github/workflows/pre-release.yml | 28 ++++++++++++++++++++++++++++ .github/workflows/release.yml | 25 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 9095bbad1..e294ab8e2 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -86,6 +86,21 @@ jobs: type=edge type=semver,pattern=v{{version}},value=${{ env.VERSION }} + - name: Collect Docker image metadata (extern-sync) + id: meta-extern-sync + uses: docker/metadata-action@v5 + with: + images: ${{ env.BASE_IMAGE_NAME }}-extern-sync + labels: | + org.opencontainers.image.created=${{ env.COMMITED_AT }} + org.opencontainers.image.version=v${{ env.VERSION }} + org.opencontainers.image.maintainer=GeoWerkstatt GmbH + flavor: | + latest=false + tags: | + type=edge + type=semver,pattern=v{{version}},value=${{ env.VERSION }} + - name: Log in to the GitHub container registry uses: docker/login-action@v3 with: @@ -146,6 +161,19 @@ jobs: cache-from: type=registry,ref=${{ env.BASE_IMAGE_NAME }}-view-sync:edge cache-to: type=inline + - name: Build and push Docker image (extern-sync) + uses: docker/build-push-action@v5 + with: + context: ./src/extern-sync + push: true + build-args: | + VERSION=${{ env.VERSION }} + REVISION=${{ env.REVISION }} + tags: ${{ steps.meta-extern-sync.outputs.tags }} + labels: ${{ steps.meta-extern-sync.outputs.labels }} + cache-from: type=registry,ref=${{ env.BASE_IMAGE_NAME }}-extern-sync:edge + cache-to: type=inline + - name: Create GitHub pre-release run: | gh api \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1186638d5..9ea5a551a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,6 +78,18 @@ jobs: tags: | type=semver,pattern=v{{version}} + - name: Collect Docker image metadata (extern-sync) + id: meta-extern-sync + uses: docker/metadata-action@v5 + with: + images: ${{ env.BASE_IMAGE_NAME }}-extern-sync + labels: | + org.opencontainers.image.created=${{ env.COMMITED_AT }} + org.opencontainers.image.version=v${{ env.VERSION }} + org.opencontainers.image.maintainer=GeoWerkstatt GmbH + tags: | + type=semver,pattern=v{{version}} + - name: Log in to the GitHub container registry uses: docker/login-action@v3 with: @@ -138,6 +150,19 @@ jobs: cache-from: type=registry,ref=${{ env.BASE_IMAGE_NAME }}-view-sync:edge cache-to: type=inline + - name: Build and push Docker image (extern-sync) + uses: docker/build-push-action@v5 + with: + context: ./src/extern-sync + push: true + build-args: | + VERSION=${{ env.VERSION }} + REVISION=${{ env.REVISION }} + tags: ${{ steps.meta-extern-sync.outputs.tags }} + labels: ${{ steps.meta-extern-sync.outputs.labels }} + cache-from: type=registry,ref=${{ env.BASE_IMAGE_NAME }}-extern-sync:edge + cache-to: type=inline + patch-changelog: runs-on: ubuntu-latest name: Patch CHANGELOG.md and update GitHub release notes From 2ecfc33b16d89293c9afba773628bdd70e7e8604 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Fri, 15 Nov 2024 10:24:10 +0100 Subject: [PATCH 07/24] Add environment for integration tests --- src/extern-sync/docker-compose.services.yml | 34 +++++++++++++++++++++ src/extern-sync/docker-compose.yml | 13 ++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/extern-sync/docker-compose.services.yml create mode 100644 src/extern-sync/docker-compose.yml diff --git a/src/extern-sync/docker-compose.services.yml b/src/extern-sync/docker-compose.services.yml new file mode 100644 index 000000000..5e6b7920f --- /dev/null +++ b/src/extern-sync/docker-compose.services.yml @@ -0,0 +1,34 @@ +services: + db: + extends: + file: ../../docker-compose.yml + service: db + # override the default configuration + volumes: + - /var/lib/postgresql/data + target-db: + image: postgis/postgis:15-3.4-alpine + restart: unless-stopped + ports: + - 5433:5432 + volumes: + - ../../db:/docker-entrypoint-initdb.d + environment: + POSTGRES_USER: CHOCOLATESLAW + POSTGRES_PASSWORD: FRUGALCLUSTER + POSTGRES_DB: WAXDIONYSUS + api: + extends: + file: ../../docker-compose.yml + service: api + build: + args: + - VERSION=0.0.99 + - REVISION=test + environment: + # use development environment to enable seeding the database + - ASPNETCORE_ENVIRONMENT=Development + minio: + extends: + file: ../../docker-compose.yml + service: minio diff --git a/src/extern-sync/docker-compose.yml b/src/extern-sync/docker-compose.yml new file mode 100644 index 000000000..89274793b --- /dev/null +++ b/src/extern-sync/docker-compose.yml @@ -0,0 +1,13 @@ +services: + extern-sync: + build: + context: ../ + dockerfile: extern-sync/Dockerfile + args: + - VERSION=0.0.99 + - REVISION=dev + environment: + - CONNECTIONSTRINGS__SourceBdmsContext=Host=host.docker.internal;Port=5432;Database=bdms;Username=SPAWNPLOW;Password=YELLOWSPATULA + - CONNECTIONSTRINGS__TargetBdmsContext=Host=host.docker.internal;Port=5433;Database=WAXDIONYSUS;Username=CHOCOLATESLAW;Password=FRUGALCLUSTER + extra_hosts: + - "host.docker.internal:host-gateway" From f8c291d742412b4c01d4c0ba71e650509fab9e14 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Fri, 15 Nov 2024 10:02:12 +0100 Subject: [PATCH 08/24] Add GitHub action integration test --- .github/workflows/ci.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c2c05d12..31dec870f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,3 +92,18 @@ jobs: - name: Run view-sync working-directory: src/view-sync run: docker compose -f docker-compose.yml up --build --exit-code-from view-sync + + extern-sync: + runs-on: ubuntu-latest + name: Run extern-sync + + steps: + - uses: actions/checkout@v4 + + - name: Setup environment + working-directory: src/extern-sync + run: docker compose -f docker-compose.services.yml up --build --wait + + - name: Run extern-sync + working-directory: src/extern-sync + run: docker compose -f docker-compose.yml up --build --exit-code-from extern-sync From 7c00a7335d197a5609f7d28acbd0a73064b9450b Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Fri, 15 Nov 2024 10:02:40 +0100 Subject: [PATCH 09/24] Add documentation --- src/extern-sync/README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/extern-sync/README.md diff --git a/src/extern-sync/README.md b/src/extern-sync/README.md new file mode 100644 index 000000000..d18accf1e --- /dev/null +++ b/src/extern-sync/README.md @@ -0,0 +1,30 @@ +# Extern-Sync + +## 🎯 Purpose + +*Extern-Sync* is a .NET application to sync selected drilling data between a source and a target database. It consists of a couple of *sync tasks* that are executed in sequence. + +The application is packed into a Docker container and can be run in a containerized environment. The container exits with a non-zero exit code if any of the *sync tasks* fails. + +## 🛠️ Configuration + +The application is configured using environment variables. The following environment variables are mandatory: + +- `CONNECTIONSTRINGS__SourceBdmsContext`: The connection string to the source database. +- `CONNECTIONSTRINGS__TargetBdmsContext`: The connection string to the target database. + +## 🧪 Unit Tests + +According to your needs, *sync tasks* can either be executed in a *in-memory* or a real PostgreSQL database using [Testcontainers](https://testcontainers.com/modules/postgresql/). + +## 🚀 Usage + +A full integration test can be run by executing the following commands: + +```bash +# Setup environment (source and target databases) +docker compose -f docker-compose.services.yml up --wait + +# Start the extern-sync container +docker compose down && docker compose up --build +``` From f844185a315dbbcd0b02ddd445f4ff3614d00a63 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Tue, 19 Nov 2024 08:48:12 +0100 Subject: [PATCH 10/24] Add pre-configured pgAdmin in order to be able to inspect source and target database. --- src/extern-sync/config/pgadmin4-servers.json | 22 ++++++++++++++++++++ src/extern-sync/docker-compose.services.yml | 20 ++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/extern-sync/config/pgadmin4-servers.json diff --git a/src/extern-sync/config/pgadmin4-servers.json b/src/extern-sync/config/pgadmin4-servers.json new file mode 100644 index 000000000..c7304f64b --- /dev/null +++ b/src/extern-sync/config/pgadmin4-servers.json @@ -0,0 +1,22 @@ +{ + "Servers": { + "1": { + "Name": "source-db", + "Group": "Servers", + "Host": "db", + "Port": 5432, + "MaintenanceDB": "bdms", + "Username": "SPAWNPLOW", + "PassFile": "/tmp/.pgpass" + }, + "2": { + "Name": "target-db", + "Group": "Servers", + "Host": "target-db", + "Port": 5432, + "MaintenanceDB": "WAXDIONYSUS", + "Username": "CHOCOLATESLAW", + "PassFile": "/tmp/.pgpass" + } + } + } diff --git a/src/extern-sync/docker-compose.services.yml b/src/extern-sync/docker-compose.services.yml index 5e6b7920f..33937ab54 100644 --- a/src/extern-sync/docker-compose.services.yml +++ b/src/extern-sync/docker-compose.services.yml @@ -17,6 +17,26 @@ services: POSTGRES_USER: CHOCOLATESLAW POSTGRES_PASSWORD: FRUGALCLUSTER POSTGRES_DB: WAXDIONYSUS + pgadmin: + image: dpage/pgadmin4 + restart: unless-stopped + ports: + - 3050:80 + environment: + PGADMIN_DEFAULT_EMAIL: pgadmin@example.com + PGADMIN_DEFAULT_PASSWORD: ROADBOUNCE + PGADMIN_CONFIG_SERVER_MODE: "False" + PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False" + volumes: + - ./config/pgadmin4-servers.json:/pgadmin4/servers.json + entrypoint: + - /bin/sh + - -c + - | + /bin/echo 'db:5432:bdms:SPAWNPLOW:YELLOWSPATULA' > /tmp/.pgpass + /bin/echo 'target-db:5432:WAXDIONYSUS:CHOCOLATESLAW:FRUGALCLUSTER' >> /tmp/.pgpass + chmod 0600 /tmp/.pgpass + /entrypoint.sh api: extends: file: ../../docker-compose.yml From a0a5de803b9ef2b749f254ed5963316de3561f49 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Tue, 19 Nov 2024 09:40:56 +0100 Subject: [PATCH 11/24] Use collection-specific `TrueForAll` method to improve performance. Ref.: Sonar csharpsquid:S6603 --- src/extern-sync/Tasks/SynchronizeUsers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extern-sync/Tasks/SynchronizeUsers.cs b/src/extern-sync/Tasks/SynchronizeUsers.cs index 3eff008c7..63a16b338 100644 --- a/src/extern-sync/Tasks/SynchronizeUsers.cs +++ b/src/extern-sync/Tasks/SynchronizeUsers.cs @@ -19,7 +19,7 @@ protected override async Task RunTaskAsync(CancellationToken cancellationToken) var targetUsers = await Target.Users.ToListAsync(cancellationToken).ConfigureAwait(false); var usersToInsert = sourceUsers - .Where(sourceUser => targetUsers.All(targetUser => targetUser.Id != sourceUser.Id)) + .Where(sourceUser => targetUsers.TrueForAll(targetUser => targetUser.Id != sourceUser.Id)) .ToList(); Target.Users.AddRange(usersToInsert); From df8bf9a51e2a497a0e03c4e771558a04d0f02c95 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Tue, 19 Nov 2024 11:31:24 +0100 Subject: [PATCH 12/24] Rename sync tasks --- src/extern-sync/Program.cs | 10 +++++----- ...CollectInformation.cs => CollectInformationTask.cs} | 2 +- .../Tasks/{MigrateDatabase.cs => SetupDatabaseTask.cs} | 5 +++-- .../{SynchronizeUsers.cs => SynchronizeUsersTask.cs} | 2 +- .../{UpdateSequences.cs => UpdateSequencesTask.cs} | 2 +- ...nformationTest.cs => CollectInformationTaskTest.cs} | 4 ++-- ...grateDatabaseTest.cs => MigrateDatabaseTaskTest.cs} | 4 ++-- ...hronizeUsersTest.cs => SynchronizeUsersTaskTest.cs} | 4 ++-- ...dateSequencesTest.cs => UpdateSequencesTaskTest.cs} | 6 +++--- 9 files changed, 20 insertions(+), 19 deletions(-) rename src/extern-sync/Tasks/{CollectInformation.cs => CollectInformationTask.cs} (91%) rename src/extern-sync/Tasks/{MigrateDatabase.cs => SetupDatabaseTask.cs} (89%) rename src/extern-sync/Tasks/{SynchronizeUsers.cs => SynchronizeUsersTask.cs} (92%) rename src/extern-sync/Tasks/{UpdateSequences.cs => UpdateSequencesTask.cs} (94%) rename tests/extern-sync/Tasks/{CollectInformationTest.cs => CollectInformationTaskTest.cs} (70%) rename tests/extern-sync/Tasks/{MigrateDatabaseTest.cs => MigrateDatabaseTaskTest.cs} (71%) rename tests/extern-sync/Tasks/{SynchronizeUsersTest.cs => SynchronizeUsersTaskTest.cs} (87%) rename tests/extern-sync/Tasks/{UpdateSequencesTest.cs => UpdateSequencesTaskTest.cs} (80%) diff --git a/src/extern-sync/Program.cs b/src/extern-sync/Program.cs index b131a3dc7..8b96f3589 100644 --- a/src/extern-sync/Program.cs +++ b/src/extern-sync/Program.cs @@ -1,4 +1,4 @@ -using BDMS.ExternSync; +using BDMS.ExternSync; using BDMS.ExternSync.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -18,10 +18,10 @@ string GetConnectionString(string name) => services.AddTransient(); // Register tasks. The order specified here is the order in which they will be executed. - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); // Register task manager services.AddScoped(); diff --git a/src/extern-sync/Tasks/CollectInformation.cs b/src/extern-sync/Tasks/CollectInformationTask.cs similarity index 91% rename from src/extern-sync/Tasks/CollectInformation.cs rename to src/extern-sync/Tasks/CollectInformationTask.cs index 054f205df..ab0ae3061 100644 --- a/src/extern-sync/Tasks/CollectInformation.cs +++ b/src/extern-sync/Tasks/CollectInformationTask.cs @@ -6,7 +6,7 @@ namespace BDMS.ExternSync.Tasks; /// Collects some information about the source and target databases /// and checks if they are not the same. /// -public class CollectInformation(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) +public class CollectInformationTask(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) { /// protected override async Task RunTaskAsync(CancellationToken cancellationToken) diff --git a/src/extern-sync/Tasks/MigrateDatabase.cs b/src/extern-sync/Tasks/SetupDatabaseTask.cs similarity index 89% rename from src/extern-sync/Tasks/MigrateDatabase.cs rename to src/extern-sync/Tasks/SetupDatabaseTask.cs index ff434c26c..c620a0050 100644 --- a/src/extern-sync/Tasks/MigrateDatabase.cs +++ b/src/extern-sync/Tasks/SetupDatabaseTask.cs @@ -5,13 +5,14 @@ namespace BDMS.ExternSync.Tasks; /// -/// Checks whether the source and target databases have the same schema version. +/// Setups the target database by migrating the schema and checks whether the source +/// and target databases have the same schema version. /// /// /// IMPORTANT! This class does not yet implement the actual behavior. It only /// contains sample code to verify the testing and integration concepts. /// -public class MigrateDatabase(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) +public class SetupDatabaseTask(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) { /// protected override async Task RunTaskAsync(CancellationToken cancellationToken) diff --git a/src/extern-sync/Tasks/SynchronizeUsers.cs b/src/extern-sync/Tasks/SynchronizeUsersTask.cs similarity index 92% rename from src/extern-sync/Tasks/SynchronizeUsers.cs rename to src/extern-sync/Tasks/SynchronizeUsersTask.cs index 63a16b338..b7f967231 100644 --- a/src/extern-sync/Tasks/SynchronizeUsers.cs +++ b/src/extern-sync/Tasks/SynchronizeUsersTask.cs @@ -10,7 +10,7 @@ namespace BDMS.ExternSync.Tasks; /// IMPORTANT! This class does not yet implement the actual behavior. It only /// contains sample code to verify the testing and integration concepts. /// -public class SynchronizeUsers(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) +public class SynchronizeUsersTask(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) { /// protected override async Task RunTaskAsync(CancellationToken cancellationToken) diff --git a/src/extern-sync/Tasks/UpdateSequences.cs b/src/extern-sync/Tasks/UpdateSequencesTask.cs similarity index 94% rename from src/extern-sync/Tasks/UpdateSequences.cs rename to src/extern-sync/Tasks/UpdateSequencesTask.cs index 10da0047a..b2fb7de23 100644 --- a/src/extern-sync/Tasks/UpdateSequences.cs +++ b/src/extern-sync/Tasks/UpdateSequencesTask.cs @@ -11,7 +11,7 @@ namespace BDMS.ExternSync.Tasks; /// IMPORTANT! This class does not yet implement the actual behavior. It only /// contains sample code to verify the testing and integration concepts. /// -public class UpdateSequences(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) +public class UpdateSequencesTask(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) { private const int minValue = 20000; private const string sequenceName = "users_id_usr_seq"; diff --git a/tests/extern-sync/Tasks/CollectInformationTest.cs b/tests/extern-sync/Tasks/CollectInformationTaskTest.cs similarity index 70% rename from tests/extern-sync/Tasks/CollectInformationTest.cs rename to tests/extern-sync/Tasks/CollectInformationTaskTest.cs index cdda52503..15e88b73f 100644 --- a/tests/extern-sync/Tasks/CollectInformationTest.cs +++ b/tests/extern-sync/Tasks/CollectInformationTaskTest.cs @@ -5,13 +5,13 @@ namespace BDMS.ExternSync.Test; [TestClass] -public class CollectInformationTest +public class CollectInformationTaskTest { [TestMethod] public async Task CollectInformation() { using var syncContext = await SyncContext.BuildAsync().ConfigureAwait(false); - using var syncTask = new CollectInformation(syncContext, new Mock>().Object); + using var syncTask = new CollectInformationTask(syncContext, new Mock>().Object); await syncTask.ExecuteAndValidateAsync(Mock.Of().Token); } diff --git a/tests/extern-sync/Tasks/MigrateDatabaseTest.cs b/tests/extern-sync/Tasks/MigrateDatabaseTaskTest.cs similarity index 71% rename from tests/extern-sync/Tasks/MigrateDatabaseTest.cs rename to tests/extern-sync/Tasks/MigrateDatabaseTaskTest.cs index 015e29d5f..ad4fe3e7b 100644 --- a/tests/extern-sync/Tasks/MigrateDatabaseTest.cs +++ b/tests/extern-sync/Tasks/MigrateDatabaseTaskTest.cs @@ -5,13 +5,13 @@ namespace BDMS.ExternSync.Test; [TestClass] -public class MigrateDatabaseTest +public class MigrateDatabaseTaskTest { [TestMethod] public async Task MigrateDatabase() { using var syncContext = await SyncContext.BuildAsync().ConfigureAwait(false); - using var syncTask = new MigrateDatabase(syncContext, new Mock>().Object); + using var syncTask = new SetupDatabaseTask(syncContext, new Mock>().Object); await syncTask.ExecuteAndValidateAsync(Mock.Of().Token); } diff --git a/tests/extern-sync/Tasks/SynchronizeUsersTest.cs b/tests/extern-sync/Tasks/SynchronizeUsersTaskTest.cs similarity index 87% rename from tests/extern-sync/Tasks/SynchronizeUsersTest.cs rename to tests/extern-sync/Tasks/SynchronizeUsersTaskTest.cs index 882226e65..12737f78c 100644 --- a/tests/extern-sync/Tasks/SynchronizeUsersTest.cs +++ b/tests/extern-sync/Tasks/SynchronizeUsersTaskTest.cs @@ -6,13 +6,13 @@ namespace BDMS.ExternSync.Test; [TestClass] -public class SynchronizeUsersTest +public class SynchronizeUsersTaskTest { [TestMethod] public async Task SynchronizeUsers() { using var syncContext = await SyncContext.BuildAsync(useInMemory: true).ConfigureAwait(false); - using var syncTask = new SynchronizeUsers(syncContext, Mock.Of>()); + using var syncTask = new SynchronizeUsersTask(syncContext, Mock.Of>()); await syncContext.SeedUserTestDataAsync().ConfigureAwait(false); var (source, target) = (syncContext.Source, syncContext.Target); diff --git a/tests/extern-sync/Tasks/UpdateSequencesTest.cs b/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs similarity index 80% rename from tests/extern-sync/Tasks/UpdateSequencesTest.cs rename to tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs index 6e4417d89..af0a869cd 100644 --- a/tests/extern-sync/Tasks/UpdateSequencesTest.cs +++ b/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs @@ -1,4 +1,4 @@ -using BDMS.ExternSync.Tasks; +using BDMS.ExternSync.Tasks; using Microsoft.Extensions.Logging; using Moq; using Npgsql; @@ -6,13 +6,13 @@ namespace BDMS.ExternSync.Test; [TestClass] -public class UpdateSequencesTest +public class UpdateSequencesTaskTest { [TestMethod] public async Task UpdateSequences() { using var syncContext = await SyncContext.BuildAsync().ConfigureAwait(false); - using var syncTask = new UpdateSequences(syncContext, new Mock>().Object); + using var syncTask = new UpdateSequencesTask(syncContext, new Mock>().Object); var targetDbConnection = await syncContext.Target.GetDbConnectionAsync().ConfigureAwait(false); using var selectCommand = new NpgsqlCommand($"SELECT last_value FROM bdms.users_id_usr_seq;", targetDbConnection); From 3eebbeebcf507525a35120a40215d15c0c836a6a Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Tue, 19 Nov 2024 11:36:14 +0100 Subject: [PATCH 13/24] Move helper methods into more suitable files. --- src/extern-sync/Program.cs | 4 +- src/extern-sync/SyncContext.cs | 3 +- ...textHelpers.cs => SyncContextConstants.cs} | 20 ++------- src/extern-sync/SyncContextExtensions.cs | 15 ++++++- src/extern-sync/Tasks/UpdateSequencesTask.cs | 2 +- tests/extern-sync/SyncContext.cs | 2 +- tests/extern-sync/SyncContextExtensions.cs | 37 ++++++++++++++++ tests/extern-sync/SyncContextHelpers.cs | 44 ------------------- 8 files changed, 61 insertions(+), 66 deletions(-) rename src/extern-sync/{SyncContextHelpers.cs => SyncContextConstants.cs} (52%) delete mode 100644 tests/extern-sync/SyncContextHelpers.cs diff --git a/src/extern-sync/Program.cs b/src/extern-sync/Program.cs index 8b96f3589..feabb3ba7 100644 --- a/src/extern-sync/Program.cs +++ b/src/extern-sync/Program.cs @@ -1,10 +1,10 @@ -using BDMS.ExternSync; +using BDMS.ExternSync; using BDMS.ExternSync.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using static BDMS.ExternSync.SyncContextHelpers; +using static BDMS.ExternSync.SyncContextConstants; using var app = Host.CreateDefaultBuilder(args).ConfigureServices((context, services) => { diff --git a/src/extern-sync/SyncContext.cs b/src/extern-sync/SyncContext.cs index 41e485ae2..56d2dd473 100644 --- a/src/extern-sync/SyncContext.cs +++ b/src/extern-sync/SyncContext.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using System.Data.Common; -using static BDMS.ExternSync.SyncContextHelpers; +using static BDMS.ExternSync.SyncContextConstants; +using static BDMS.ExternSync.SyncContextExtensions; namespace BDMS.ExternSync; diff --git a/src/extern-sync/SyncContextHelpers.cs b/src/extern-sync/SyncContextConstants.cs similarity index 52% rename from src/extern-sync/SyncContextHelpers.cs rename to src/extern-sync/SyncContextConstants.cs index d843f6ac2..0f2cd3a1f 100644 --- a/src/extern-sync/SyncContextHelpers.cs +++ b/src/extern-sync/SyncContextConstants.cs @@ -1,13 +1,11 @@ -using Microsoft.EntityFrameworkCore; -using Npgsql; -using System.Data.Common; +using Npgsql; namespace BDMS.ExternSync; /// -/// helper methods."/> +/// constants. /// -public static class SyncContextHelpers +public static class SyncContextConstants { /// /// The name of the source . @@ -37,15 +35,5 @@ public static class SyncContextHelpers /// public const string BoreholesDatabaseSchemaName = "bdms"; - /// - /// Gets the for the specified - /// - public static DbContextOptions GetDbContextOptions(DbConnection dbConnection) => - new DbContextOptionsBuilder().UseNpgsql(dbConnection, SyncContextExtensions.SetDbContextOptions).Options; - - /// - /// Gets the for the specified - /// - public static DbContextOptions GetDbContextOptions(string connectionString) => - new DbContextOptionsBuilder().UseNpgsql(connectionString, SyncContextExtensions.SetDbContextOptions).Options; + } diff --git a/src/extern-sync/SyncContextExtensions.cs b/src/extern-sync/SyncContextExtensions.cs index 5462916f3..e671a668a 100644 --- a/src/extern-sync/SyncContextExtensions.cs +++ b/src/extern-sync/SyncContextExtensions.cs @@ -2,7 +2,8 @@ using Npgsql; using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using System.Data; -using static BDMS.ExternSync.SyncContextHelpers; +using System.Data.Common; +using static BDMS.ExternSync.SyncContextConstants; namespace BDMS.ExternSync; @@ -56,4 +57,16 @@ public static async Task CleanUpSuperfluousDataAsync(this BdmsContext context, C context.Users.RemoveRange(usersToRemove); await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } + + /// + /// Gets the for the specified + /// + public static DbContextOptions GetDbContextOptions(DbConnection dbConnection) => + new DbContextOptionsBuilder().UseNpgsql(dbConnection, SyncContextExtensions.SetDbContextOptions).Options; + + /// + /// Gets the for the specified + /// + public static DbContextOptions GetDbContextOptions(string connectionString) => + new DbContextOptionsBuilder().UseNpgsql(connectionString, SyncContextExtensions.SetDbContextOptions).Options; } diff --git a/src/extern-sync/Tasks/UpdateSequencesTask.cs b/src/extern-sync/Tasks/UpdateSequencesTask.cs index b2fb7de23..c6242bd68 100644 --- a/src/extern-sync/Tasks/UpdateSequencesTask.cs +++ b/src/extern-sync/Tasks/UpdateSequencesTask.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Logging; using Npgsql; -using static BDMS.ExternSync.SyncContextHelpers; +using static BDMS.ExternSync.SyncContextConstants; namespace BDMS.ExternSync.Tasks; diff --git a/tests/extern-sync/SyncContext.cs b/tests/extern-sync/SyncContext.cs index 5fd161ccb..15d37eb3e 100644 --- a/tests/extern-sync/SyncContext.cs +++ b/tests/extern-sync/SyncContext.cs @@ -1,4 +1,4 @@ -using static BDMS.ExternSync.Test.SyncContextHelpers; +using static BDMS.ExternSync.Test.SyncContextExtensions; namespace BDMS.ExternSync.Test; diff --git a/tests/extern-sync/SyncContextExtensions.cs b/tests/extern-sync/SyncContextExtensions.cs index fd15ce9d2..88e7f28b0 100644 --- a/tests/extern-sync/SyncContextExtensions.cs +++ b/tests/extern-sync/SyncContextExtensions.cs @@ -1,4 +1,9 @@ using BDMS.Models; +using DotNet.Testcontainers.Builders; +using Microsoft.EntityFrameworkCore; +using Testcontainers.PostgreSql; +using static BDMS.ExternSync.SyncContextConstants; +using static BDMS.ExternSync.SyncContextExtensions; namespace BDMS.ExternSync.Test; @@ -20,4 +25,36 @@ internal static async Task SeedUserTestDataAsync(this SyncContext syncContext) target.Users.Add(new User { Id = 1, FirstName = "Charlie", LastName = "Brown", Name = "Charlie Brown", SubjectId = "brown123" }); await target.SaveChangesAsync().ConfigureAwait(false); } + + /// + /// Creates a new for testing purposes. Use to specify + /// whether to use a real PostgreSQL database or an in-memory context. + /// + internal async static Task CreateDbContextAsync(bool useInMemory) => + useInMemory ? CreateInMemoryDbContext() : await CreatePostgreSqlDbContextAsync().ConfigureAwait(false); + + private static BdmsContext CreateInMemoryDbContext() => + new(new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); + + private async static Task CreatePostgreSqlDbContextAsync() + { + var postgreSqlContainer = await CreatePostgreSqlContainerAsync().ConfigureAwait(false); + var context = new BdmsContext(GetDbContextOptions(postgreSqlContainer.GetConnectionString())); + await context.Database.MigrateAsync().ConfigureAwait(false); + await context.CleanUpSuperfluousDataAsync().ConfigureAwait(false); + return context; + } + + private static async Task CreatePostgreSqlContainerAsync() + { + var initDbDirectoryPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "initdb.d")); + var postgreSqlContainer = new PostgreSqlBuilder() + .WithImage("postgis/postgis:15-3.4-alpine") + .WithDatabase(BoreholesDatabaseName) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(5432)) + .WithResourceMapping(initDbDirectoryPath, "/docker-entrypoint-initdb.d") + .Build(); + await postgreSqlContainer.StartAsync(); + return postgreSqlContainer; + } } diff --git a/tests/extern-sync/SyncContextHelpers.cs b/tests/extern-sync/SyncContextHelpers.cs deleted file mode 100644 index 5f814061e..000000000 --- a/tests/extern-sync/SyncContextHelpers.cs +++ /dev/null @@ -1,44 +0,0 @@ -using DotNet.Testcontainers.Builders; -using Microsoft.EntityFrameworkCore; -using Testcontainers.PostgreSql; -using static BDMS.ExternSync.SyncContextHelpers; - -namespace BDMS.ExternSync.Test; - -/// -/// helper methods."/> -/// -internal static class SyncContextHelpers -{ - /// - /// Creates a new for testing purposes. Use to specify - /// whether to use a real PostgreSQL database or an in-memory context. - /// - internal async static Task CreateDbContextAsync(bool useInMemory) => - useInMemory ? CreateInMemoryDbContext() : await CreatePostgreSqlDbContextAsync().ConfigureAwait(false); - - private static BdmsContext CreateInMemoryDbContext() => - new(new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); - - private async static Task CreatePostgreSqlDbContextAsync() - { - var postgreSqlContainer = await CreatePostgreSqlContainerAsync().ConfigureAwait(false); - var context = new BdmsContext(GetDbContextOptions(postgreSqlContainer.GetConnectionString())); - await context.Database.MigrateAsync().ConfigureAwait(false); - await context.CleanUpSuperfluousDataAsync().ConfigureAwait(false); - return context; - } - - private static async Task CreatePostgreSqlContainerAsync() - { - var initDbDirectoryPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "initdb.d")); - var postgreSqlContainer = new PostgreSqlBuilder() - .WithImage("postgis/postgis:15-3.4-alpine") - .WithDatabase(BoreholesDatabaseName) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(5432)) - .WithResourceMapping(initDbDirectoryPath, "/docker-entrypoint-initdb.d") - .Build(); - await postgreSqlContainer.StartAsync(); - return postgreSqlContainer; - } -} From 547a672cfd93f1714e1996de3beee40ac04d93d9 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Tue, 19 Nov 2024 11:56:10 +0100 Subject: [PATCH 14/24] Customize build settings using Directory.Build.props --- .github/workflows/pre-release.yml | 2 +- docker-compose.yml | 4 +++- src/Directory.Build.props | 28 ++++++++++++++++++++++++++ src/api/BDMS.csproj | 19 ----------------- src/api/Dockerfile | 6 +++--- src/extern-sync/BDMS.ExternSync.csproj | 15 -------------- src/extern-sync/Dockerfile | 2 +- 7 files changed, 36 insertions(+), 40 deletions(-) create mode 100644 src/Directory.Build.props diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index e294ab8e2..57d9702ba 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -124,7 +124,7 @@ jobs: - name: Build and push Docker image (api) uses: docker/build-push-action@v5 with: - context: ./src/api + context: ./src push: true build-args: | VERSION=${{ env.VERSION }} diff --git a/docker-compose.yml b/docker-compose.yml index a232575c5..5ab5526d6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -93,12 +93,14 @@ services: DB_PORT: 5432 api: build: - context: ./src/api + context: ./src + dockerfile: api/Dockerfile target: development ports: - 5000:5000 volumes: - ./src/api:/src + - ./src/Directory.Build.props:/Directory.Build.props - /src/bin - /src/obj depends_on: diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..1a80e118f --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + 8.0-all + true + GeoWerkstatt GmbH + GeoWerkstatt GmbH + $(MSBuildThisFileDirectory)/artifacts + https://github.com/swisstopo/swissgeol-boreholes-suite + https://github.com/swisstopo/swissgeol-boreholes-suite.git + git + true + + + + + all + runtime; build; native; contentfiles; analyzers + + + <_Parameter1>$(AssemblyName).Test + + + + diff --git a/src/api/BDMS.csproj b/src/api/BDMS.csproj index fba774a22..c17c54603 100644 --- a/src/api/BDMS.csproj +++ b/src/api/BDMS.csproj @@ -1,19 +1,7 @@ - net8.0 - enable - enable - true - 8.0-all CS1591,CS8618,CS8620 BDMS - GeoWerkstatt GmbH - GeoWerkstatt GmbH - $(MSBuildThisFileDirectory)/artifacts - https://github.com/swisstopo/swissgeol-boreholes-suite - https://github.com/swisstopo/swissgeol-boreholes-suite.git - git - true Linux ..\.. @@ -32,13 +20,6 @@ - - all - runtime; build; native; contentfiles; analyzers - - - <_Parameter1>$(AssemblyName).Test - diff --git a/src/api/Dockerfile b/src/api/Dockerfile index ca855eace..1b7b8c811 100644 --- a/src/api/Dockerfile +++ b/src/api/Dockerfile @@ -9,7 +9,7 @@ RUN dotnet tool install --global dotnet-ef --version 8.0.0 ENV PATH=$PATH:/root/.dotnet/tools # Restore dependencies and tools -COPY BDMS.csproj . +COPY api/BDMS.csproj Directory.Build.props ./ RUN dotnet restore ENTRYPOINT ["dotnet", "watch", "run", "--no-launch-profile"] @@ -20,11 +20,11 @@ ARG REVISION WORKDIR /src # Restore dependencies and tools -COPY BDMS.csproj . +COPY api/BDMS.csproj Directory.Build.props ./ RUN dotnet restore # Create optimized production build -COPY . . +COPY api/ Directory.Build.props ./ RUN dotnet publish \ -c Release \ -p:UseAppHost=false \ diff --git a/src/extern-sync/BDMS.ExternSync.csproj b/src/extern-sync/BDMS.ExternSync.csproj index 7aaff74c8..324d7ef86 100644 --- a/src/extern-sync/BDMS.ExternSync.csproj +++ b/src/extern-sync/BDMS.ExternSync.csproj @@ -1,20 +1,8 @@ - net8.0 - enable - enable - false true - 8.0-all BDMS.ExternSync - GeoWerkstatt GmbH - GeoWerkstatt GmbH - $(MSBuildThisFileDirectory)/artifacts - https://github.com/swisstopo/swissgeol-boreholes-suite - https://github.com/swisstopo/swissgeol-boreholes-suite.git - git - true Exe @@ -27,9 +15,6 @@ - - <_Parameter1>$(AssemblyName).Test - diff --git a/src/extern-sync/Dockerfile b/src/extern-sync/Dockerfile index 88669188c..e98ebcd96 100644 --- a/src/extern-sync/Dockerfile +++ b/src/extern-sync/Dockerfile @@ -6,7 +6,7 @@ ARG REVISION WORKDIR /src # Restore dependencies and tools -COPY --parents "extern-sync/BDMS.ExternSync.csproj" "api/BDMS.csproj" ./ +COPY --parents "extern-sync/BDMS.ExternSync.csproj" "api/BDMS.csproj" "Directory.Build.props" ./ RUN dotnet restore "./extern-sync/BDMS.ExternSync.csproj" # Create optimized production build From 8c90ed9e23d3f0c63a6aa988d223a66c670adf24 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Tue, 19 Nov 2024 12:14:21 +0100 Subject: [PATCH 15/24] Fix StyleCop offenses --- src/extern-sync/ISyncContext.cs | 2 +- src/extern-sync/Program.cs | 2 +- src/extern-sync/SyncContext.cs | 10 ++++++-- src/extern-sync/SyncContextConstants.cs | 2 -- src/extern-sync/SyncContextExtensions.cs | 11 +++++---- src/extern-sync/SyncTask.cs | 6 +++-- src/extern-sync/SyncTaskManager.cs | 2 +- src/extern-sync/Tasks/UpdateSequencesTask.cs | 24 ++++++++++---------- 8 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/extern-sync/ISyncContext.cs b/src/extern-sync/ISyncContext.cs index 7221335e8..e157a09dd 100644 --- a/src/extern-sync/ISyncContext.cs +++ b/src/extern-sync/ISyncContext.cs @@ -2,7 +2,7 @@ /// /// Represents a boreholes sync context containing a source and -/// a target database . +/// a target database . /// public interface ISyncContext { diff --git a/src/extern-sync/Program.cs b/src/extern-sync/Program.cs index feabb3ba7..c6cedb02d 100644 --- a/src/extern-sync/Program.cs +++ b/src/extern-sync/Program.cs @@ -22,7 +22,7 @@ string GetConnectionString(string name) => services.AddScoped(); services.AddScoped(); services.AddScoped(); - + // Register task manager services.AddScoped(); }) diff --git a/src/extern-sync/SyncContext.cs b/src/extern-sync/SyncContext.cs index 56d2dd473..abd8b3510 100644 --- a/src/extern-sync/SyncContext.cs +++ b/src/extern-sync/SyncContext.cs @@ -5,6 +5,10 @@ namespace BDMS.ExternSync; +/// +/// Represents a boreholes sync context containing a source and +/// a target database . +/// public class SyncContext( [FromKeyedServices(SourceBdmsContextName)] DbConnection sourceDbConnection, [FromKeyedServices(TargetBdmsContextName)] DbConnection targetDbConnection) @@ -18,7 +22,9 @@ public class SyncContext( /// public BdmsContext Target { get; } = new BdmsContext(GetDbContextOptions(targetDbConnection)); - /// + /// + /// Disposes the and database contexts. + /// protected virtual void Dispose(bool disposing) { if (!disposedValue && disposing) @@ -26,7 +32,7 @@ protected virtual void Dispose(bool disposing) Source.Dispose(); Target.Dispose(); - disposedValue=true; + disposedValue = true; } } diff --git a/src/extern-sync/SyncContextConstants.cs b/src/extern-sync/SyncContextConstants.cs index 0f2cd3a1f..53d8b4644 100644 --- a/src/extern-sync/SyncContextConstants.cs +++ b/src/extern-sync/SyncContextConstants.cs @@ -34,6 +34,4 @@ public static class SyncContextConstants /// The name of the boreholes database schema. /// public const string BoreholesDatabaseSchemaName = "bdms"; - - } diff --git a/src/extern-sync/SyncContextExtensions.cs b/src/extern-sync/SyncContextExtensions.cs index e671a668a..f5c3a83ec 100644 --- a/src/extern-sync/SyncContextExtensions.cs +++ b/src/extern-sync/SyncContextExtensions.cs @@ -8,7 +8,7 @@ namespace BDMS.ExternSync; /// -/// extension methods."/> +/// extension methods. /// public static class SyncContextExtensions { @@ -25,20 +25,21 @@ public static void SetDbContextOptions(this NpgsqlDbContextOptionsBuilder option /// /// Gets the for the specified . /// - public async static Task GetDbConnectionAsync(this BdmsContext context, CancellationToken cancellationToken = default) + public static async Task GetDbConnectionAsync(this BdmsContext context, CancellationToken cancellationToken = default) { var databaseConnection = (NpgsqlConnection)context.Database.GetDbConnection(); if (databaseConnection.State != ConnectionState.Open) { await databaseConnection.OpenAsync(cancellationToken).ConfigureAwait(false); } + return databaseConnection; } /// /// Gets the database schema version for the specified . /// - public async static Task GetDbSchemaVersionAsync(this BdmsContext context, CancellationToken cancellationToken = default) + public static async Task GetDbSchemaVersionAsync(this BdmsContext context, CancellationToken cancellationToken = default) { var migrations = await context.Database.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false); return migrations.LastOrDefault(); @@ -59,13 +60,13 @@ public static async Task CleanUpSuperfluousDataAsync(this BdmsContext context, C } /// - /// Gets the for the specified + /// Gets the for the specified . /// public static DbContextOptions GetDbContextOptions(DbConnection dbConnection) => new DbContextOptionsBuilder().UseNpgsql(dbConnection, SyncContextExtensions.SetDbContextOptions).Options; /// - /// Gets the for the specified + /// Gets the for the specified . /// public static DbContextOptions GetDbContextOptions(string connectionString) => new DbContextOptionsBuilder().UseNpgsql(connectionString, SyncContextExtensions.SetDbContextOptions).Options; diff --git a/src/extern-sync/SyncTask.cs b/src/extern-sync/SyncTask.cs index f98591f5e..400e60c7f 100644 --- a/src/extern-sync/SyncTask.cs +++ b/src/extern-sync/SyncTask.cs @@ -45,7 +45,9 @@ public async Task ExecuteAndValidateAsync(CancellationToken cancellationToken) /// protected abstract Task ValidateTaskAsync(CancellationToken cancellationToken); - /// + /// + /// Disposes the and database contexts. + /// protected virtual void Dispose(bool disposing) { if (!disposedValue && disposing) @@ -53,7 +55,7 @@ protected virtual void Dispose(bool disposing) Source.Dispose(); Target.Dispose(); - disposedValue=true; + disposedValue = true; } } diff --git a/src/extern-sync/SyncTaskManager.cs b/src/extern-sync/SyncTaskManager.cs index 1637f6ff2..a02928e71 100644 --- a/src/extern-sync/SyncTaskManager.cs +++ b/src/extern-sync/SyncTaskManager.cs @@ -10,7 +10,7 @@ namespace BDMS.ExternSync; public class SyncTaskManager(IEnumerable tasks, ILogger logger) { /// - /// Executes the s in sequence." + /// Executes the s in sequence. /// public async Task ExecuteTasksAsync(CancellationToken cancellationToken) { diff --git a/src/extern-sync/Tasks/UpdateSequencesTask.cs b/src/extern-sync/Tasks/UpdateSequencesTask.cs index c6242bd68..e35503c04 100644 --- a/src/extern-sync/Tasks/UpdateSequencesTask.cs +++ b/src/extern-sync/Tasks/UpdateSequencesTask.cs @@ -13,36 +13,36 @@ namespace BDMS.ExternSync.Tasks; /// public class UpdateSequencesTask(ISyncContext syncContext, ILogger logger) : SyncTask(syncContext, logger) { - private const int minValue = 20000; - private const string sequenceName = "users_id_usr_seq"; - private const string getSequenceLastValueQuery = $"SELECT last_value FROM {BoreholesDatabaseSchemaName}.{sequenceName};"; - private const string setSequenceValueQuery = $"SELECT setval('{BoreholesDatabaseSchemaName}.{sequenceName}', ($1));"; + private const int MinValue = 20000; + private const string SequenceName = "users_id_usr_seq"; + private const string GetSequenceLastValueSql = $"SELECT last_value FROM {BoreholesDatabaseSchemaName}.{SequenceName};"; + private const string SetSequenceValueQuery = $"SELECT setval('{BoreholesDatabaseSchemaName}.{SequenceName}', ($1));"; /// protected override async Task RunTaskAsync(CancellationToken cancellationToken) { var targetDbConnection = await Target.GetDbConnectionAsync(cancellationToken).ConfigureAwait(false); - using var selectCommand = new NpgsqlCommand(getSequenceLastValueQuery, targetDbConnection); + using var selectCommand = new NpgsqlCommand(GetSequenceLastValueSql, targetDbConnection); var lastValue = await selectCommand.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false) as long? ?? -1; - Logger.LogInformation("{SchemaName}.{SequenceName} last_value: <{LastValue}>", BoreholesDatabaseSchemaName, sequenceName, lastValue); + Logger.LogInformation("{SchemaName}.{SequenceName} last_value: <{LastValue}>", BoreholesDatabaseSchemaName, SequenceName, lastValue); if (lastValue == -1) { - Logger.LogError("Error while reading sequence {SchemaName}.{SequenceName}.", BoreholesDatabaseSchemaName, sequenceName); + Logger.LogError("Error while reading sequence {SchemaName}.{SequenceName}.", BoreholesDatabaseSchemaName, SequenceName); } - else if (lastValue < minValue) + else if (lastValue < MinValue) { - using var alterCommand = new NpgsqlCommand(setSequenceValueQuery, targetDbConnection); - alterCommand.Parameters.AddWithValue(minValue); + using var alterCommand = new NpgsqlCommand(SetSequenceValueQuery, targetDbConnection); + alterCommand.Parameters.AddWithValue(MinValue); await alterCommand.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); - Logger.LogInformation("Sequence {SchemaName}.{SequenceName} has been set to {MinValue}.", BoreholesDatabaseSchemaName, sequenceName, minValue); + Logger.LogInformation("Sequence {SchemaName}.{SequenceName} has been set to {MinValue}.", BoreholesDatabaseSchemaName, SequenceName, MinValue); } else { - Logger.LogInformation("Sequence for {SchemaName}.{SequenceName} has already been set.", BoreholesDatabaseSchemaName, sequenceName); + Logger.LogInformation("Sequence for {SchemaName}.{SequenceName} has already been set.", BoreholesDatabaseSchemaName, SequenceName); } } From 84be02eac9b6bc3f9b6a6b22d44fac4b49d02cdc Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Tue, 19 Nov 2024 13:58:45 +0100 Subject: [PATCH 16/24] Optimize RUN command --- src/api/Dockerfile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/api/Dockerfile b/src/api/Dockerfile index 1b7b8c811..ff99779c5 100644 --- a/src/api/Dockerfile +++ b/src/api/Dockerfile @@ -3,9 +3,12 @@ ENV ASPNETCORE_ENVIRONMENT=Development WORKDIR /src # Install missing packages -RUN apt-get -y update -RUN apt-get -y install git vim curl htop -RUN dotnet tool install --global dotnet-ef --version 8.0.0 +RUN \ + apt-get -y update && \ + apt-get -y install curl git htop vim && \ + rm -rf /var/lib/apt/lists/* && \ + dotnet tool install --global dotnet-ef --version 8.0.0 + ENV PATH=$PATH:/root/.dotnet/tools # Restore dependencies and tools From 3c06830da823a5fb1756cfae87c710f5343a4bce Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Thu, 21 Nov 2024 09:06:09 +0100 Subject: [PATCH 17/24] Improve readability --- src/extern-sync/Program.cs | 3 +-- src/extern-sync/SyncContextExtensions.cs | 12 ++++++------ src/extern-sync/Tasks/CollectInformationTask.cs | 8 ++++---- src/extern-sync/Tasks/UpdateSequencesTask.cs | 2 +- tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs | 2 +- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/extern-sync/Program.cs b/src/extern-sync/Program.cs index c6cedb02d..b5bdc262a 100644 --- a/src/extern-sync/Program.cs +++ b/src/extern-sync/Program.cs @@ -10,8 +10,7 @@ { // Register source and target database contexts string GetConnectionString(string name) => - context.Configuration.GetConnectionString(name) ?? - throw new InvalidOperationException($"Connection string <{name}> not found."); + context.Configuration.GetConnectionString(name) ?? throw new InvalidOperationException($"Connection string <{name}> not found."); services.AddNpgsqlDataSource(GetConnectionString(SourceBdmsContextName), serviceKey: SourceBdmsContextName); services.AddNpgsqlDataSource(GetConnectionString(TargetBdmsContextName), serviceKey: TargetBdmsContextName); diff --git a/src/extern-sync/SyncContextExtensions.cs b/src/extern-sync/SyncContextExtensions.cs index f5c3a83ec..e5b56e923 100644 --- a/src/extern-sync/SyncContextExtensions.cs +++ b/src/extern-sync/SyncContextExtensions.cs @@ -23,17 +23,17 @@ public static void SetDbContextOptions(this NpgsqlDbContextOptionsBuilder option } /// - /// Gets the for the specified . + /// Gets and opens the for the specified . /// - public static async Task GetDbConnectionAsync(this BdmsContext context, CancellationToken cancellationToken = default) + public static async Task GetAndOpenDbConnectionAsync(this BdmsContext context, CancellationToken cancellationToken = default) { - var databaseConnection = (NpgsqlConnection)context.Database.GetDbConnection(); - if (databaseConnection.State != ConnectionState.Open) + var dbConnection = (NpgsqlConnection)context.Database.GetDbConnection(); + if (dbConnection.State != ConnectionState.Open) { - await databaseConnection.OpenAsync(cancellationToken).ConfigureAwait(false); + await dbConnection.OpenAsync(cancellationToken).ConfigureAwait(false); } - return databaseConnection; + return dbConnection; } /// diff --git a/src/extern-sync/Tasks/CollectInformationTask.cs b/src/extern-sync/Tasks/CollectInformationTask.cs index ab0ae3061..147359760 100644 --- a/src/extern-sync/Tasks/CollectInformationTask.cs +++ b/src/extern-sync/Tasks/CollectInformationTask.cs @@ -12,8 +12,8 @@ public class CollectInformationTask(ISyncContext syncContext, ILogger protected override async Task RunTaskAsync(CancellationToken cancellationToken) { - var targetDbConnection = await Target.GetDbConnectionAsync(cancellationToken).ConfigureAwait(false); + var targetDbConnection = await Target.GetAndOpenDbConnectionAsync(cancellationToken).ConfigureAwait(false); using var selectCommand = new NpgsqlCommand(GetSequenceLastValueSql, targetDbConnection); var lastValue = await selectCommand.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false) as long? ?? -1; diff --git a/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs b/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs index af0a869cd..54fd0e419 100644 --- a/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs +++ b/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs @@ -14,7 +14,7 @@ public async Task UpdateSequences() using var syncContext = await SyncContext.BuildAsync().ConfigureAwait(false); using var syncTask = new UpdateSequencesTask(syncContext, new Mock>().Object); - var targetDbConnection = await syncContext.Target.GetDbConnectionAsync().ConfigureAwait(false); + var targetDbConnection = await syncContext.Target.GetAndOpenDbConnectionAsync().ConfigureAwait(false); using var selectCommand = new NpgsqlCommand($"SELECT last_value FROM bdms.users_id_usr_seq;", targetDbConnection); Assert.AreEqual((long)8, await selectCommand.ExecuteScalarAsync()); From 2bd529eff32e8c21500fbb63968eacb5d4d2ace6 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Thu, 21 Nov 2024 09:19:47 +0100 Subject: [PATCH 18/24] Customize build settings for test projects using Directory.Build.props --- tests/Directory.Build.props | 27 +++++++++++++++++++ tests/api/BDMS.Test.csproj | 20 +------------- tests/extern-sync/BDMS.ExternSync.Test.csproj | 15 ----------- 3 files changed, 28 insertions(+), 34 deletions(-) create mode 100644 tests/Directory.Build.props diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 000000000..6149fc16b --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + $(MSBuildProjectName.Replace(" ", "_").Replace(".Test", "")) + true + false + true + CS1591,CS8604,CS8618,CS8620,CS8629,CA1001,CA1014,CS8625,CA2000,CA2007 + true + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + diff --git a/tests/api/BDMS.Test.csproj b/tests/api/BDMS.Test.csproj index 7b0ad1167..68c1d325a 100644 --- a/tests/api/BDMS.Test.csproj +++ b/tests/api/BDMS.Test.csproj @@ -1,26 +1,8 @@ - - true - net8.0 - enable - enable - false - BDMS - true - CS1591,CS8604,CS8618,CS8620,CS8629,CA1001,CA1014,CS8625,CA2000,CA2007 - + - - - - - - - all - runtime; build; native; contentfiles; analyzers - diff --git a/tests/extern-sync/BDMS.ExternSync.Test.csproj b/tests/extern-sync/BDMS.ExternSync.Test.csproj index eb03615ea..c11a03fad 100644 --- a/tests/extern-sync/BDMS.ExternSync.Test.csproj +++ b/tests/extern-sync/BDMS.ExternSync.Test.csproj @@ -1,23 +1,8 @@ - - net8.0 - enable - enable - - false - true - $(MSBuildProjectName.Replace(" ", "_")) - - - - - - - From 0a5212ceca5b2b514a694d8469b8c997fec95fe6 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Thu, 21 Nov 2024 09:20:36 +0100 Subject: [PATCH 19/24] Fix StyleCop offenses --- tests/extern-sync/SyncContext.cs | 6 ++++-- tests/extern-sync/SyncContextExtensions.cs | 6 +++--- tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/extern-sync/SyncContext.cs b/tests/extern-sync/SyncContext.cs index 15d37eb3e..5af430190 100644 --- a/tests/extern-sync/SyncContext.cs +++ b/tests/extern-sync/SyncContext.cs @@ -41,7 +41,9 @@ public static async Task BuildAsync(bool useInMemory = false) return new SyncContext(source.Result, target.Result); } - /// + /// + /// Disposes the and database contexts. + /// protected virtual void Dispose(bool disposing) { if (!disposedValue && disposing) @@ -49,7 +51,7 @@ protected virtual void Dispose(bool disposing) Source.Dispose(); Target.Dispose(); - disposedValue=true; + disposedValue = true; } } diff --git a/tests/extern-sync/SyncContextExtensions.cs b/tests/extern-sync/SyncContextExtensions.cs index 88e7f28b0..c84a5491b 100644 --- a/tests/extern-sync/SyncContextExtensions.cs +++ b/tests/extern-sync/SyncContextExtensions.cs @@ -8,7 +8,7 @@ namespace BDMS.ExternSync.Test; /// -/// extension methods."/> +/// extension methods. /// internal static class SyncContextExtensions { @@ -30,13 +30,13 @@ internal static async Task SeedUserTestDataAsync(this SyncContext syncContext) /// Creates a new for testing purposes. Use to specify /// whether to use a real PostgreSQL database or an in-memory context. /// - internal async static Task CreateDbContextAsync(bool useInMemory) => + internal static async Task CreateDbContextAsync(bool useInMemory) => useInMemory ? CreateInMemoryDbContext() : await CreatePostgreSqlDbContextAsync().ConfigureAwait(false); private static BdmsContext CreateInMemoryDbContext() => new(new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); - private async static Task CreatePostgreSqlDbContextAsync() + private static async Task CreatePostgreSqlDbContextAsync() { var postgreSqlContainer = await CreatePostgreSqlContainerAsync().ConfigureAwait(false); var context = new BdmsContext(GetDbContextOptions(postgreSqlContainer.GetConnectionString())); diff --git a/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs b/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs index 54fd0e419..9564f9bb4 100644 --- a/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs +++ b/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs @@ -17,10 +17,10 @@ public async Task UpdateSequences() var targetDbConnection = await syncContext.Target.GetAndOpenDbConnectionAsync().ConfigureAwait(false); using var selectCommand = new NpgsqlCommand($"SELECT last_value FROM bdms.users_id_usr_seq;", targetDbConnection); - Assert.AreEqual((long)8, await selectCommand.ExecuteScalarAsync()); + Assert.AreEqual(8L, await selectCommand.ExecuteScalarAsync()); await syncTask.ExecuteAndValidateAsync(Mock.Of().Token); - Assert.AreEqual((long)20000, await selectCommand.ExecuteScalarAsync()); + Assert.AreEqual(20000L, await selectCommand.ExecuteScalarAsync()); } } From 502b5f44cf9fa2d640b82c89a2b383ea7e74d813 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Thu, 21 Nov 2024 10:40:59 +0100 Subject: [PATCH 20/24] Change namespace for test classes to match the one from the assembly which is tested. --- tests/extern-sync/SyncTaskManagerTest.cs | 2 +- .../Tasks/CollectInformationTaskTest.cs | 4 ++-- .../Tasks/MigrateDatabaseTaskTest.cs | 4 ++-- .../Tasks/SynchronizeUsersTaskTest.cs | 4 ++-- .../Tasks/UpdateSequencesTaskTest.cs | 4 ++-- .../{SyncContext.cs => TestSyncContext.cs} | 18 +++++++++--------- ...ensions.cs => TestSyncContextExtensions.cs} | 8 ++++---- 7 files changed, 22 insertions(+), 22 deletions(-) rename tests/extern-sync/{SyncContext.cs => TestSyncContext.cs} (70%) rename tests/extern-sync/{SyncContextExtensions.cs => TestSyncContextExtensions.cs} (92%) diff --git a/tests/extern-sync/SyncTaskManagerTest.cs b/tests/extern-sync/SyncTaskManagerTest.cs index 338d0537b..d3179139f 100644 --- a/tests/extern-sync/SyncTaskManagerTest.cs +++ b/tests/extern-sync/SyncTaskManagerTest.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Logging; using Moq; -namespace BDMS.ExternSync.Test; +namespace BDMS.ExternSync; [TestClass] public class SyncTaskManagerTest diff --git a/tests/extern-sync/Tasks/CollectInformationTaskTest.cs b/tests/extern-sync/Tasks/CollectInformationTaskTest.cs index 15e88b73f..28978db78 100644 --- a/tests/extern-sync/Tasks/CollectInformationTaskTest.cs +++ b/tests/extern-sync/Tasks/CollectInformationTaskTest.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.Logging; using Moq; -namespace BDMS.ExternSync.Test; +namespace BDMS.ExternSync; [TestClass] public class CollectInformationTaskTest @@ -10,7 +10,7 @@ public class CollectInformationTaskTest [TestMethod] public async Task CollectInformation() { - using var syncContext = await SyncContext.BuildAsync().ConfigureAwait(false); + using var syncContext = await TestSyncContext.BuildAsync().ConfigureAwait(false); using var syncTask = new CollectInformationTask(syncContext, new Mock>().Object); await syncTask.ExecuteAndValidateAsync(Mock.Of().Token); diff --git a/tests/extern-sync/Tasks/MigrateDatabaseTaskTest.cs b/tests/extern-sync/Tasks/MigrateDatabaseTaskTest.cs index ad4fe3e7b..7b563e03d 100644 --- a/tests/extern-sync/Tasks/MigrateDatabaseTaskTest.cs +++ b/tests/extern-sync/Tasks/MigrateDatabaseTaskTest.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.Logging; using Moq; -namespace BDMS.ExternSync.Test; +namespace BDMS.ExternSync; [TestClass] public class MigrateDatabaseTaskTest @@ -10,7 +10,7 @@ public class MigrateDatabaseTaskTest [TestMethod] public async Task MigrateDatabase() { - using var syncContext = await SyncContext.BuildAsync().ConfigureAwait(false); + using var syncContext = await TestSyncContext.BuildAsync().ConfigureAwait(false); using var syncTask = new SetupDatabaseTask(syncContext, new Mock>().Object); await syncTask.ExecuteAndValidateAsync(Mock.Of().Token); diff --git a/tests/extern-sync/Tasks/SynchronizeUsersTaskTest.cs b/tests/extern-sync/Tasks/SynchronizeUsersTaskTest.cs index 12737f78c..031b7d803 100644 --- a/tests/extern-sync/Tasks/SynchronizeUsersTaskTest.cs +++ b/tests/extern-sync/Tasks/SynchronizeUsersTaskTest.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging; using Moq; -namespace BDMS.ExternSync.Test; +namespace BDMS.ExternSync; [TestClass] public class SynchronizeUsersTaskTest @@ -11,7 +11,7 @@ public class SynchronizeUsersTaskTest [TestMethod] public async Task SynchronizeUsers() { - using var syncContext = await SyncContext.BuildAsync(useInMemory: true).ConfigureAwait(false); + using var syncContext = await TestSyncContext.BuildAsync(useInMemory: true).ConfigureAwait(false); using var syncTask = new SynchronizeUsersTask(syncContext, Mock.Of>()); await syncContext.SeedUserTestDataAsync().ConfigureAwait(false); diff --git a/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs b/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs index 9564f9bb4..9c04afd6d 100644 --- a/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs +++ b/tests/extern-sync/Tasks/UpdateSequencesTaskTest.cs @@ -3,7 +3,7 @@ using Moq; using Npgsql; -namespace BDMS.ExternSync.Test; +namespace BDMS.ExternSync; [TestClass] public class UpdateSequencesTaskTest @@ -11,7 +11,7 @@ public class UpdateSequencesTaskTest [TestMethod] public async Task UpdateSequences() { - using var syncContext = await SyncContext.BuildAsync().ConfigureAwait(false); + using var syncContext = await TestSyncContext.BuildAsync().ConfigureAwait(false); using var syncTask = new UpdateSequencesTask(syncContext, new Mock>().Object); var targetDbConnection = await syncContext.Target.GetAndOpenDbConnectionAsync().ConfigureAwait(false); diff --git a/tests/extern-sync/SyncContext.cs b/tests/extern-sync/TestSyncContext.cs similarity index 70% rename from tests/extern-sync/SyncContext.cs rename to tests/extern-sync/TestSyncContext.cs index 5af430190..036afa65d 100644 --- a/tests/extern-sync/SyncContext.cs +++ b/tests/extern-sync/TestSyncContext.cs @@ -1,13 +1,13 @@ -using static BDMS.ExternSync.Test.SyncContextExtensions; +using static BDMS.ExternSync.TestSyncContextExtensions; -namespace BDMS.ExternSync.Test; +namespace BDMS.ExternSync; /// -/// Represents a containing a source and target +/// Represents a containing a source and target /// for testing purposes. The /// can either use a real PostgreSQL database or an in-memory context. /// -internal class SyncContext : ISyncContext, IDisposable +internal class TestSyncContext : ISyncContext, IDisposable { private bool disposedValue; @@ -18,27 +18,27 @@ internal class SyncContext : ISyncContext, IDisposable public BdmsContext Target { get; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - private SyncContext(BdmsContext source, BdmsContext target) + private TestSyncContext(BdmsContext source, BdmsContext target) { Source = source; Target = target; } /// - /// Builds a new instance of the class. + /// Builds a new instance of the class. /// /// By default a real PostgreSQL database is used /// when source and target database contexts are created. This allows to execute /// raw SQL queries but comes with a performance penalty. When set to true /// an in-memory database is used instead. - public static async Task BuildAsync(bool useInMemory = false) + public static async Task BuildAsync(bool useInMemory = false) { var source = CreateDbContextAsync(useInMemory); var target = CreateDbContextAsync(useInMemory); await Task.WhenAll(source, target).ConfigureAwait(false); - return new SyncContext(source.Result, target.Result); + return new TestSyncContext(source.Result, target.Result); } /// diff --git a/tests/extern-sync/SyncContextExtensions.cs b/tests/extern-sync/TestSyncContextExtensions.cs similarity index 92% rename from tests/extern-sync/SyncContextExtensions.cs rename to tests/extern-sync/TestSyncContextExtensions.cs index c84a5491b..73ade309d 100644 --- a/tests/extern-sync/SyncContextExtensions.cs +++ b/tests/extern-sync/TestSyncContextExtensions.cs @@ -5,14 +5,14 @@ using static BDMS.ExternSync.SyncContextConstants; using static BDMS.ExternSync.SyncContextExtensions; -namespace BDMS.ExternSync.Test; +namespace BDMS.ExternSync; /// -/// extension methods. +/// extension methods. /// -internal static class SyncContextExtensions +internal static class TestSyncContextExtensions { - internal static async Task SeedUserTestDataAsync(this SyncContext syncContext) + internal static async Task SeedUserTestDataAsync(this TestSyncContext syncContext) { var (source, target) = (syncContext.Source, syncContext.Target); From d30383a877bb5bd91881a9ee9705011701fef373 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Thu, 21 Nov 2024 11:19:45 +0100 Subject: [PATCH 21/24] Reuse creating db context options --- src/api/BdmsContext.cs | 3 ++- src/api/BdmsContextConstants.cs | 17 +++++++++++++++++ src/api/BdmsContextExtensions.cs | 14 +++++++++++++- src/api/Program.cs | 7 +------ src/extern-sync/SyncContextConstants.cs | 10 ---------- src/extern-sync/SyncContextExtensions.cs | 16 ++-------------- src/extern-sync/Tasks/UpdateSequencesTask.cs | 2 +- tests/api/ContextFactory.cs | 14 ++------------ tests/extern-sync/TestSyncContextExtensions.cs | 2 +- 9 files changed, 39 insertions(+), 46 deletions(-) create mode 100644 src/api/BdmsContextConstants.cs diff --git a/src/api/BdmsContext.cs b/src/api/BdmsContext.cs index 3509d7edb..33ecb3aea 100644 --- a/src/api/BdmsContext.cs +++ b/src/api/BdmsContext.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using Npgsql; using System.Security.Claims; +using static BDMS.BdmsContextConstants; namespace BDMS; @@ -96,7 +97,7 @@ public async Task UpdateChangeInformationAndSaveChangesAsync(HttpContext ht /// protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.HasDefaultSchema("bdms"); + modelBuilder.HasDefaultSchema(BoreholesDatabaseSchemaName); modelBuilder.Entity().HasKey(k => new { k.UserId, k.WorkgroupId, k.Role }); modelBuilder.Entity().HasKey(k => new { k.UserId, k.TermId }); diff --git a/src/api/BdmsContextConstants.cs b/src/api/BdmsContextConstants.cs new file mode 100644 index 000000000..7045ed0c1 --- /dev/null +++ b/src/api/BdmsContextConstants.cs @@ -0,0 +1,17 @@ +namespace BDMS; + +/// +/// constants. +/// +public static class BdmsContextConstants +{ + /// + /// The name of the boreholes database. + /// + public const string BoreholesDatabaseName = "bdms"; + + /// + /// The name of the boreholes database schema. + /// + public const string BoreholesDatabaseSchemaName = "bdms"; +} diff --git a/src/api/BdmsContextExtensions.cs b/src/api/BdmsContextExtensions.cs index 22e497fa0..783ac425c 100644 --- a/src/api/BdmsContextExtensions.cs +++ b/src/api/BdmsContextExtensions.cs @@ -4,17 +4,29 @@ using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using NetTopologySuite.Geometries; +using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using System.Collections.ObjectModel; using System.Globalization; +using static BDMS.BdmsContextConstants; namespace BDMS; #pragma warning disable CA1505 /// -/// Contains extensions methods for the BDMS db context. +/// Contains extensions methods for the . /// public static class BdmsContextExtensions { + /// + /// Sets the default options for the boreholes database context. + /// + public static void SetDbContextOptions(this NpgsqlDbContextOptionsBuilder options) + { + options.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + options.UseNetTopologySuite(); + options.MigrationsHistoryTable("__EFMigrationsHistory", BoreholesDatabaseName); + } + /// /// Seed test data but only if the database is not yet seeded. /// diff --git a/src/api/Program.cs b/src/api/Program.cs index 43abdddd8..e8c1f083b 100644 --- a/src/api/Program.cs +++ b/src/api/Program.cs @@ -52,12 +52,7 @@ builder.Services.AddHttpClient(); var connectionString = builder.Configuration.GetConnectionString(nameof(BdmsContext)); -builder.Services.AddNpgsql(connectionString, options => -{ - options.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); - options.UseNetTopologySuite(); - options.MigrationsHistoryTable("__EFMigrationsHistory", "bdms"); -}); +builder.Services.AddNpgsql(connectionString, BdmsContextExtensions.SetDbContextOptions); builder.Services.Configure(options => options.LowercaseUrls = true); builder.Services.AddSwaggerGen(options => diff --git a/src/extern-sync/SyncContextConstants.cs b/src/extern-sync/SyncContextConstants.cs index 53d8b4644..0b90701a9 100644 --- a/src/extern-sync/SyncContextConstants.cs +++ b/src/extern-sync/SyncContextConstants.cs @@ -24,14 +24,4 @@ public static class SyncContextConstants /// services with different connection strings. /// public const string TargetBdmsContextName = "TargetBdmsContext"; - - /// - /// The name of the boreholes database. - /// - public const string BoreholesDatabaseName = "bdms"; - - /// - /// The name of the boreholes database schema. - /// - public const string BoreholesDatabaseSchemaName = "bdms"; } diff --git a/src/extern-sync/SyncContextExtensions.cs b/src/extern-sync/SyncContextExtensions.cs index e5b56e923..24e41047d 100644 --- a/src/extern-sync/SyncContextExtensions.cs +++ b/src/extern-sync/SyncContextExtensions.cs @@ -1,9 +1,7 @@ using Microsoft.EntityFrameworkCore; using Npgsql; -using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure; using System.Data; using System.Data.Common; -using static BDMS.ExternSync.SyncContextConstants; namespace BDMS.ExternSync; @@ -12,16 +10,6 @@ namespace BDMS.ExternSync; /// public static class SyncContextExtensions { - /// - /// Sets the options for the boreholes database context. - /// - public static void SetDbContextOptions(this NpgsqlDbContextOptionsBuilder options) - { - options.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); - options.UseNetTopologySuite(); - options.MigrationsHistoryTable("__EFMigrationsHistory", BoreholesDatabaseName); - } - /// /// Gets and opens the for the specified . /// @@ -63,11 +51,11 @@ public static async Task CleanUpSuperfluousDataAsync(this BdmsContext context, C /// Gets the for the specified . /// public static DbContextOptions GetDbContextOptions(DbConnection dbConnection) => - new DbContextOptionsBuilder().UseNpgsql(dbConnection, SyncContextExtensions.SetDbContextOptions).Options; + new DbContextOptionsBuilder().UseNpgsql(dbConnection, options => BdmsContextExtensions.SetDbContextOptions(options)).Options; /// /// Gets the for the specified . /// public static DbContextOptions GetDbContextOptions(string connectionString) => - new DbContextOptionsBuilder().UseNpgsql(connectionString, SyncContextExtensions.SetDbContextOptions).Options; + new DbContextOptionsBuilder().UseNpgsql(connectionString, BdmsContextExtensions.SetDbContextOptions).Options; } diff --git a/src/extern-sync/Tasks/UpdateSequencesTask.cs b/src/extern-sync/Tasks/UpdateSequencesTask.cs index 80b759369..f24b30ff6 100644 --- a/src/extern-sync/Tasks/UpdateSequencesTask.cs +++ b/src/extern-sync/Tasks/UpdateSequencesTask.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Logging; using Npgsql; -using static BDMS.ExternSync.SyncContextConstants; +using static BDMS.BdmsContextConstants; namespace BDMS.ExternSync.Tasks; diff --git a/tests/api/ContextFactory.cs b/tests/api/ContextFactory.cs index 415d7e5c8..aac3281ba 100644 --- a/tests/api/ContextFactory.cs +++ b/tests/api/ContextFactory.cs @@ -10,18 +10,8 @@ internal static class ContextFactory /// Creates an instance of . /// /// The initialized . - public static BdmsContext CreateContext() - { - return new BdmsContext( - new DbContextOptionsBuilder().UseNpgsql( - ConnectionString, - options => - { - options.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); - options.UseNetTopologySuite(); - options.MigrationsHistoryTable("__EFMigrationsHistory", "bdms"); - }).Options); - } + public static BdmsContext CreateContext() => + new(new DbContextOptionsBuilder().UseNpgsql(ConnectionString, BdmsContextExtensions.SetDbContextOptions).Options); /// /// Creates a new DbContext and starts a transaction. diff --git a/tests/extern-sync/TestSyncContextExtensions.cs b/tests/extern-sync/TestSyncContextExtensions.cs index 73ade309d..ba638732b 100644 --- a/tests/extern-sync/TestSyncContextExtensions.cs +++ b/tests/extern-sync/TestSyncContextExtensions.cs @@ -2,7 +2,7 @@ using DotNet.Testcontainers.Builders; using Microsoft.EntityFrameworkCore; using Testcontainers.PostgreSql; -using static BDMS.ExternSync.SyncContextConstants; +using static BDMS.BdmsContextConstants; using static BDMS.ExternSync.SyncContextExtensions; namespace BDMS.ExternSync; From 88c056fdddbadf0fadd14bc61097b6700da1f13f Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Thu, 21 Nov 2024 13:17:57 +0100 Subject: [PATCH 22/24] Consolidate dotnet test rules --- tests/.editorconfig | 8 ++++++++ tests/Directory.Build.props | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/.editorconfig b/tests/.editorconfig index a2ebeeb9f..3daea8b75 100644 --- a/tests/.editorconfig +++ b/tests/.editorconfig @@ -3,9 +3,17 @@ [*.cs] dotnet_diagnostic.CA1001.severity = none +dotnet_diagnostic.CA1014.severity = none dotnet_diagnostic.CA1801.severity = none dotnet_diagnostic.CA1804.severity = none +dotnet_diagnostic.CA2000.severity = none +dotnet_diagnostic.CA2007.severity = none +dotnet_diagnostic.CS1591.severity = none +dotnet_diagnostic.CS8604.severity = none dotnet_diagnostic.CS8618.severity = none +dotnet_diagnostic.CS8620.severity = none +dotnet_diagnostic.CS8625.severity = none +dotnet_diagnostic.CS8629.severity = none dotnet_diagnostic.SA0001.severity = none dotnet_diagnostic.IDE0051.severity = none dotnet_diagnostic.IDE0060.severity = none diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 6149fc16b..4a6c2507e 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -8,7 +8,6 @@ true false true - CS1591,CS8604,CS8618,CS8620,CS8629,CA1001,CA1014,CS8625,CA2000,CA2007 true From 8a1756e51d7ab9fb1287bbde22d5a353ce2dcbea Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Thu, 21 Nov 2024 13:18:47 +0100 Subject: [PATCH 23/24] Remove unused test rules --- tests/.editorconfig | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/tests/.editorconfig b/tests/.editorconfig index 3daea8b75..ff49f7418 100644 --- a/tests/.editorconfig +++ b/tests/.editorconfig @@ -2,19 +2,11 @@ # Description: Ignores rules that are not useful in test code because we want to test these edge cases [*.cs] -dotnet_diagnostic.CA1001.severity = none -dotnet_diagnostic.CA1014.severity = none -dotnet_diagnostic.CA1801.severity = none -dotnet_diagnostic.CA1804.severity = none -dotnet_diagnostic.CA2000.severity = none -dotnet_diagnostic.CA2007.severity = none -dotnet_diagnostic.CS1591.severity = none -dotnet_diagnostic.CS8604.severity = none -dotnet_diagnostic.CS8618.severity = none -dotnet_diagnostic.CS8620.severity = none -dotnet_diagnostic.CS8625.severity = none -dotnet_diagnostic.CS8629.severity = none -dotnet_diagnostic.SA0001.severity = none -dotnet_diagnostic.IDE0051.severity = none -dotnet_diagnostic.IDE0060.severity = none -dotnet_diagnostic.IDE0130.severity = none + dotnet_diagnostic.CA1001.severity = none + dotnet_diagnostic.CA2000.severity = none + dotnet_diagnostic.CS1591.severity = none + dotnet_diagnostic.CS8604.severity = none + dotnet_diagnostic.CS8618.severity = none + dotnet_diagnostic.CS8620.severity = none + dotnet_diagnostic.CS8625.severity = none + dotnet_diagnostic.CS8629.severity = none From 2bb41bd9cd67e3990978151c39d4563f1fca2f58 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Thu, 21 Nov 2024 13:31:05 +0100 Subject: [PATCH 24/24] Fix indentation --- tests/.editorconfig | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/.editorconfig b/tests/.editorconfig index ff49f7418..39730ba2f 100644 --- a/tests/.editorconfig +++ b/tests/.editorconfig @@ -2,11 +2,11 @@ # Description: Ignores rules that are not useful in test code because we want to test these edge cases [*.cs] - dotnet_diagnostic.CA1001.severity = none - dotnet_diagnostic.CA2000.severity = none - dotnet_diagnostic.CS1591.severity = none - dotnet_diagnostic.CS8604.severity = none - dotnet_diagnostic.CS8618.severity = none - dotnet_diagnostic.CS8620.severity = none - dotnet_diagnostic.CS8625.severity = none - dotnet_diagnostic.CS8629.severity = none +dotnet_diagnostic.CA1001.severity = none +dotnet_diagnostic.CA2000.severity = none +dotnet_diagnostic.CS1591.severity = none +dotnet_diagnostic.CS8604.severity = none +dotnet_diagnostic.CS8618.severity = none +dotnet_diagnostic.CS8620.severity = none +dotnet_diagnostic.CS8625.severity = none +dotnet_diagnostic.CS8629.severity = none